﻿// SciTE - Scintilla based Text Editor
/** @file SciTEBase.cxx
 ** Platform independent base class of editor.
 **/
// Copyright 1998-2007 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <time.h>

#include <string>
#include <map>

#include "Platform.h"

#if PLAT_GTK

#include <unistd.h>
#include <gtk/gtk.h>

#endif

#if PLAT_WIN

#ifdef __BORLANDC__
// Borland includes Windows.h for STL and defaults to different API number
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#endif

#include <windows.h>
#include <commctrl.h>

#ifdef _MSC_VER
#include <direct.h>
#endif
#ifdef __BORLANDC__
#include <dir.h>
#endif
#ifdef __DMC__
#include <dir.h>
#endif

#endif

#include "SciTE.h"
#include "PropSet.h"
#include "SString.h"
#include "StringList.h"
#include "Accessor.h"
#include "WindowAccessor.h"
#include "Scintilla.h"
#include "ScintillaWidget.h"
#include "SciLexer.h"
#include "Extender.h"
#include "FilePath.h"
#include "PropSetFile.h"
#include "Mutex.h"
#include "JobQueue.h"
#include "SciTEBase.h"

// AddStyledText only called from About so static size buffer is OK
void AddStyledText(WindowID hwnd, const WCHAR *s, int attr) {
    WCHAR buf[1000];
    size_t len = wcslen(s);
    for (size_t i = 0; i < len; i++) {
        buf[i*2] = s[i];
        buf[i*2 + 1] = static_cast<WCHAR>(attr);
    }
    Platform::SendScintillaPointer(hwnd, SCI_ADDSTYLEDTEXT,
        static_cast<int>(len*2), const_cast<WCHAR *>(buf));
}

void SetAboutStyle(WindowID wsci, int style, ColourDesired fore) {
    Platform::SendScintilla(wsci, SCI_STYLESETFORE, style, fore.AsLong());
}

static void HackColour(int &n) {
    n += (rand() % 100) - 50;
    if (n > 0xE7)
        n = 0x60;
    if (n < 0)
        n = 0x80;
}

SciTEBase::SciTEBase(Extension *ext) : apis(true), extender(ext) {
    codePage = 0;
    characterSet = 0;
    language = L"java";
    lexLanguage = SCLEX_CPP;
    functionDefinition = 0;
    indentOpening = true;
    indentClosing = true;
	indentMaintain = false;
    statementLookback = 10;
	preprocessorSymbol = L'\0';

    fnEditor = 0;
    ptrEditor = 0;
    fnOutput = 0;
    ptrOutput = 0;
    tbVisible = false;
    sbVisible = false;
    tabVisible = false;
    tabHideOne = false;
    tabMultiLine = false;
    sbNum = 1;
    visHeightTools = 0;
	visHeightTab = 0;
    visHeightStatus = 0;
    visHeightEditor = 1;
    heightBar = 7;
    dialogsOnScreen = 0;
    topMost = false;
    wrap = false;
    wrapOutput = false;
    wrapStyle = SC_WRAP_WORD;
    isReadOnly = false;
    openFilesHere = false;
    fullScreen = false;

    heightOutput = 0;
	heightOutputStartDrag = 0;
    previousHeightOutput = 0;

    allowMenuActions = true;
    scrollOutput = 1;
    returnOutputToCommand = true;

    ptStartDrag.x = 0;
    ptStartDrag.y = 0;
    capturedMouse = false;
    firstPropertiesRead = true;
    localiser.read = false;
    splitVertical = false;
    bufferedDraw = true;
    twoPhaseDraw = true;
    bracesCheck = true;
    bracesSloppy = false;
    bracesStyle = 0;
    braceCount = 0;

    indentationWSVisible = true;
    indentExamine = SC_IV_LOOKBOTH;

    autoCompleteIgnoreCase = false;
    callTipIgnoreCase = false;
    autoCCausedByOnlyOne = false;
    startCalltipWord = 0;
    currentCallTip = 0;
    maxCallTips = 1;
    currentCallTipWord = L"";
    lastPosCallTip = 0;

    margin = false;
    marginWidth = marginWidthDefault;
    foldMargin = true;
    foldMarginWidth = foldMarginWidthDefault;
    lineNumbers = false;
    lineNumbersWidth = lineNumbersWidthDefault;
    lineNumbersExpand = false;
    usePalette = false;

    replacing = false;
    havefound = false;
    matchCase = false;
    wholeWord = false;
    reverseFind = false;
    regExp = false;
    wrapFind = true;
    unSlash = false;
    findInStyle = false;
    findStyle = 0;

    languageMenu = 0;
    languageItems = 0;

    shortCutItemList = 0;
    shortCutItems = 0;

    macrosEnabled = false;
    recording = false;

    propsBase.superPS = &propsEmbed;
    propsUser.superPS = &propsBase;
    propsDirectory.superPS = &propsUser;
    propsLocal.superPS = &propsDirectory;
    props.superPS = &propsLocal;

    propsStatus.superPS = &props;

    needReadProperties = false;
    allowAlpha = true;
}

SciTEBase::~SciTEBase() {
    if (extender)
        extender->Finalise();
    delete []languageMenu;
    delete []shortCutItemList;
    popup.Destroy();
}

sptr_t SciTEBase::SendEditor(unsigned int msg, uptr_t wParam, sptr_t lParam) {
    sptr_t retVal = fnEditor(ptrEditor, msg, wParam, lParam);
    sptr_t status = fnEditor(ptrEditor, SCI_GETSTATUS, 0, 0);
    if (status > 0)
        throw ScintillaFailure(status);
    return retVal;
}

sptr_t SciTEBase::SendEditorString(unsigned int msg, uptr_t wParam, const WCHAR *s) {
    return SendEditor(msg, wParam, reinterpret_cast<sptr_t>(s));
}

sptr_t SciTEBase::SendOutput(unsigned int msg, uptr_t wParam, sptr_t lParam) {
    sptr_t retVal = fnOutput(ptrOutput, msg, wParam, lParam);
    sptr_t status = fnOutput(ptrOutput, SCI_GETSTATUS, 0, 0);
    if (status > 0)
        throw ScintillaFailure(status);
    return retVal;
}

sptr_t SciTEBase::SendOutputString(unsigned int msg, uptr_t wParam, const WCHAR *s) {
    return SendOutput(msg, wParam, reinterpret_cast<sptr_t>(s));
}

sptr_t SciTEBase::SendFocused(unsigned int msg, uptr_t wParam, sptr_t lParam) {
    if (wOutput.HasFocus())
        return SendOutput(msg, wParam, lParam);
    else
        return SendEditor(msg, wParam, lParam);
}

sptr_t SciTEBase::SendPane(int destination, unsigned int msg, uptr_t wParam, sptr_t lParam) {
    if (destination == IDM_SRCWIN)
        return SendEditor(msg, wParam, lParam);
    else if (destination == IDM_RUNWIN)
        return SendOutput(msg, wParam, lParam);
    else
        return SendFocused(msg, wParam, lParam);
}

sptr_t SciTEBase::SendWindow(Window &w, unsigned int msg, uptr_t wParam, sptr_t lParam) {
    if (w.GetID() == wOutput.GetID())
        return SendOutput(msg, wParam, lParam);
    else
        return SendEditor(msg, wParam, lParam);
}

void SciTEBase::SendChildren(unsigned int msg, uptr_t wParam, sptr_t lParam) {
    SendEditor(msg, wParam, lParam);
    SendOutput(msg, wParam, lParam);
}

sptr_t SciTEBase::SendOutputEx(unsigned int msg, uptr_t wParam /*= 0*/, sptr_t lParam /*= 0*/, bool direct /*= true*/) {
    if (direct)
        return SendOutput(msg, wParam, lParam);
    return Platform::SendScintillaPointer(wOutput.GetID(), msg, wParam,
        reinterpret_cast<void*>(lParam));
}

SString SciTEBase::GetTranslationToAbout(const WCHAR * const propname, bool retainIfNotFound) {
#if PLAT_WIN
    // By code below, all translators can write their name in their own
    // language in locale.properties on Windows.
    SString result = localiser.Text(propname, retainIfNotFound);
/*
	if (!result.length())
        return result;
    int translationCodePage = props.GetInt(L"code.page", CP_ACP);
    int bufwSize = ::MultiByteToWideChar(translationCodePage, MB_PRECOMPOSED, result.c_str(), -1, NULL, 0);
    if (!bufwSize)
        return result;
    wchar_t *bufw = new wchar_t[bufwSize+1];
    bufwSize = ::MultiByteToWideChar(translationCodePage, MB_PRECOMPOSED, result.c_str(), -1, bufw, bufwSize);
    if (!bufwSize) {
        delete []bufw;
        return result;
    }
    int bufcSize = UTF8Length(bufw, bufwSize);
    if (!bufcSize)
        return result;
    char *bufc = new char[bufcSize+1];
    UTF8FromUCS2(bufw, bufwSize, bufc, bufcSize);
    delete []bufw;
    result = bufcSize ? bufc : "";
    delete []bufc;
*/
    return result;
#else
    // On GTK+, localiser.Text always converts to UTF-8.
    return localiser.Text(propname, retainIfNotFound);
#endif
}

void SciTEBase::SetAboutMessage(WindowID wsci, const WCHAR *appTitle) {
    if (wsci) {
        Platform::SendScintilla(wsci, SCI_SETSTYLEBITS, 7, 0);
        Platform::SendScintilla(wsci, SCI_STYLERESETDEFAULT, 0, 0);
        int fontSize = 15;

        Platform::SendScintilla(wsci, SCI_SETCODEPAGE, SC_CP_UTF8, 0);

        Platform::SendScintilla(wsci, SCI_STYLESETSIZE, STYLE_DEFAULT, fontSize);
        Platform::SendScintilla(wsci, SCI_STYLESETBACK, STYLE_DEFAULT, ColourDesired(0xff, 0xff, 0xff).AsLong());
        Platform::SendScintilla(wsci, SCI_STYLECLEARALL, 0, 0);

        SetAboutStyle(wsci, 0, ColourDesired(0xff, 0xff, 0xff));
        Platform::SendScintilla(wsci, SCI_STYLESETSIZE, 0, fontSize);
        Platform::SendScintilla(wsci, SCI_STYLESETBACK, 0, ColourDesired(0, 0, 0x80).AsLong());
        AddStyledText(wsci, appTitle, 0);
        AddStyledText(wsci, L"\n", 0);
        SetAboutStyle(wsci, 1, ColourDesired(0, 0, 0));
        int trsSty = 5; // define the stylenumber to assign font for translators.
        SString translator = GetTranslationToAbout(L"TranslationCredit", false);
        SetAboutStyle(wsci, trsSty, ColourDesired(0, 0, 0));
#if PLAT_WIN
        // On Windows Me (maybe 9x also), we must assign another font to display translation.
        if (translator.length()) {
            SString fontBase = props.GetExpanded(L"font.translators");
            StyleDefinition sd(fontBase.c_str());
            if (sd.specified & StyleDefinition::sdFont) {
                Platform::SendScintilla(wsci, SCI_STYLESETFONT, trsSty,
                    reinterpret_cast<uptr_t>(sd.font.c_str()));
            }
            if (sd.specified & StyleDefinition::sdSize) {
                Platform::SendScintilla(wsci, SCI_STYLESETSIZE, trsSty, sd.size);
            }
        }
#endif
        AddStyledText(wsci, GetTranslationToAbout(L"Version").c_str(), trsSty);
		AddStyledText(wsci, L" 1.0.1\n", 1);
        AddStyledText(wsci, L"    " TEXT(__DATE__) L" " TEXT(__TIME__) L"\n", 1);
        AddStyledText(wsci, GetTranslationToAbout(L"by").c_str(), trsSty);
        AddStyledText(wsci, L" Shuhai Shen.\n", 2);
        SetAboutStyle(wsci, 3, ColourDesired(0, 0, 0));
		AddStyledText(wsci, L"December 2009-Feburary 2010.\n", 3);
        SetAboutStyle(wsci, 4, ColourDesired(0, 0x7f, 0x7f));
        AddStyledText(wsci, L"http://leonax.net\n", 4);
        Platform::SendScintilla(wsci, SCI_SETREADONLY, 1, 0);
    }
}

void SciTEBase::ViewWhitespace(bool view) {
    if (view && indentationWSVisible)
        SendEditor(SCI_SETVIEWWS, SCWS_VISIBLEALWAYS);
    else if (view)
        SendEditor(SCI_SETVIEWWS, SCWS_VISIBLEAFTERINDENT);
    else
        SendEditor(SCI_SETVIEWWS, SCWS_INVISIBLE);
}

StyleAndWords SciTEBase::GetStyleAndWords(const WCHAR *base) {
    StyleAndWords sw;
    SString fileNameForExtension = ExtensionFileName();
    SString sAndW = props.GetNewExpand(base, fileNameForExtension.c_str());
    sw.styleNumber = sAndW.value();
    const WCHAR *space = wcschr(sAndW.c_str(), L' ');
    if (space)
        sw.words = space + 1;
    return sw;
}

void SciTEBase::AssignKey(int key, int mods, int cmd) {
    SendEditor(SCI_ASSIGNCMDKEY,
        Platform::LongFromTwoShorts(static_cast<short>(key),
        static_cast<short>(mods)), cmd);
}

/**
* Override the language of the current file with the one indicated by @a cmdID.
* Mostly used to set a language on a file of unknown extension.
*/
void SciTEBase::SetOverrideLanguage(int cmdID) {
    RecentFile rf = GetFilePosition();
    EnsureRangeVisible(0, SendEditor(SCI_GETLENGTH), false);
    // Zero all the style bytes
    SendEditor(SCI_CLEARDOCUMENTSTYLE);

    CurrentBuffer()->overrideExtension = L"x.";
    CurrentBuffer()->overrideExtension += languageMenu[cmdID].extension;
    ReadProperties();
    SetIndentSettings();
    SendEditor(SCI_COLOURISE, 0, -1);
    Redraw();
    DisplayAround(rf);
}

int SciTEBase::LengthDocument() {
    return SendEditor(SCI_GETLENGTH);
}

int SciTEBase::GetCaretInLine() {
    int caret = SendEditor(SCI_GETCURRENTPOS);
    int line = SendEditor(SCI_LINEFROMPOSITION, caret);
    int lineStart = SendEditor(SCI_POSITIONFROMLINE, line);
    return caret - lineStart;
}

void SciTEBase::GetLine(WCHAR *text, int sizeText, int line) {
    if (line < 0)
        line = GetCurrentLineNumber();
    int lineStart = SendEditor(SCI_POSITIONFROMLINE, line);
    int lineEnd = SendEditor(SCI_GETLINEENDPOSITION, line);
    int lineMax = lineStart + sizeText - 1;
    if (lineEnd > lineMax)
        lineEnd = lineMax;
    GetRange(wEditor, lineStart, lineEnd, text);
    text[lineEnd - lineStart] = L'\0';
}

SString SciTEBase::GetLine(int line) {
    int len;
    // Get needed buffer size
    if (line < 0) {
        len = Platform::SendScintilla(wEditor.GetID(),
            SCI_GETCURLINE, 0, 0);
    } else {
        len = Platform::SendScintilla(wEditor.GetID(),
            SCI_GETLINE, line, 0);
    }
    // Allocate buffer
    SBuffer text(len);
    // And get the line
    if (line < 0) {
        Platform::SendScintillaPointer(wEditor.GetID(),
            SCI_GETCURLINE, len, text.ptr());
    } else {
        Platform::SendScintillaPointer(wEditor.GetID(),
            SCI_GETLINE, line, text.ptr());
    }
    return SString(text);
}

void SciTEBase::GetRange(Window &win, int start, int end, WCHAR *text) {
    Sci_TextRange tr;
    tr.chrg.cpMin = start;
    tr.chrg.cpMax = end;
    tr.lpstrText = text;
    Platform::SendScintillaPointer(win.GetID(), SCI_GETTEXTRANGE, 0, &tr);
}

/**
* Check if the given line is a preprocessor condition line.
* @return The kind of preprocessor condition (enum values).
*/
int SciTEBase::IsLinePreprocessorCondition(WCHAR *line) {
    WCHAR *currChar = line;
    WCHAR word[32];

    if (!currChar) {
        return false;
    }
    while (isspacechar(*currChar) && *currChar) {
        currChar++;
    }
    if (preprocessorSymbol && (*currChar == preprocessorSymbol)) {
        currChar++;
        while (isspacechar(*currChar) && *currChar) {
            currChar++;
        }
        size_t i = 0;
        while (!isspacechar(*currChar) && *currChar && (i < (sizeof(word) - 1))) {
            word[i++] = *currChar++;
        }
        word[i] = L'\0';
        if (preprocCondStart.InList(word)) {
            return ppcStart;
        }
        if (preprocCondMiddle.InList(word)) {
            return ppcMiddle;
        }
        if (preprocCondEnd.InList(word)) {
            return ppcEnd;
        }
    }
    return noPPC;
}

/**
* Search a matching preprocessor condition line.
* @return @c true if the end condition are meet.
* Also set curLine to the line where one of these conditions is mmet.
*/
bool SciTEBase::FindMatchingPreprocessorCondition(
    int &curLine,   		///< Number of the line where to start the search
    int direction,   		///< Direction of search: 1 = forward, -1 = backward
    int condEnd1,   		///< First status of line for which the search is OK
    int condEnd2) {		///< Second one

        bool isInside = false;
        WCHAR line[800];	// No need for full line
        int status, level = 0;
        int maxLines = SendEditor(SCI_GETLINECOUNT) - 1;

        while (curLine < maxLines && curLine > 0 && !isInside) {
            curLine += direction;	// Increment or decrement
            GetLine(line, sizeof(line) / sizeof(WCHAR), curLine);
            status = IsLinePreprocessorCondition(line);

            if ((direction == 1 && status == ppcStart) || (direction == -1 && status == ppcEnd)) {
                level++;
            } else if (level > 0 && ((direction == 1 && status == ppcEnd) || (direction == -1 && status == ppcStart))) {
                level--;
            } else if (level == 0 && (status == condEnd1 || status == condEnd2)) {
                isInside = true;
            }
        }

        return isInside;
}

/**
* Find if there is a preprocessor condition after or before the caret position,
* @return @c true if inside a preprocessor condition.
*/
#ifdef __BORLANDC__
// Borland warns that isInside is assigned a value that is never used in this method.
// This is OK so turn off the warning just for this method.
#pragma warn -aus
#endif
bool SciTEBase::FindMatchingPreprocCondPosition(
    bool isForward,   		///< @c true if search forward
    int &mppcAtCaret,   	///< Matching preproc. cond.: current position of caret
    int &mppcMatch) {		///< Matching preproc. cond.: matching position

        bool isInside = false;
        int curLine;
        WCHAR line[800];	// Probably no need to get more characters, even if the line is longer, unless very strange layout...
        int status;

        // Get current line
        curLine = SendEditor(SCI_LINEFROMPOSITION, mppcAtCaret);
        GetLine(line, sizeof(line) / sizeof(WCHAR), curLine);
        status = IsLinePreprocessorCondition(line);

        switch (status) {
    case ppcStart:
        if (isForward) {
            isInside = FindMatchingPreprocessorCondition(curLine, 1, ppcMiddle, ppcEnd);
        } else {
            mppcMatch = mppcAtCaret;
            return true;
        }
        break;
    case ppcMiddle:
        if (isForward) {
            isInside = FindMatchingPreprocessorCondition(curLine, 1, ppcMiddle, ppcEnd);
        } else {
            isInside = FindMatchingPreprocessorCondition(curLine, -1, ppcStart, ppcMiddle);
        }
        break;
    case ppcEnd:
        if (isForward) {
            mppcMatch = mppcAtCaret;
            return true;
        } else {
            isInside = FindMatchingPreprocessorCondition(curLine, -1, ppcStart, ppcMiddle);
        }
        break;
    default:   	// Should be noPPC

        if (isForward) {
            isInside = FindMatchingPreprocessorCondition(curLine, 1, ppcMiddle, ppcEnd);
        } else {
            isInside = FindMatchingPreprocessorCondition(curLine, -1, ppcStart, ppcMiddle);
        }
        break;
        }

        if (isInside) {
            mppcMatch = SendEditor(SCI_POSITIONFROMLINE, curLine);
        }
        return isInside;
}
#ifdef __BORLANDC__
#pragma warn .aus
#endif

static bool IsBrace(WCHAR ch) {
    return ch == L'[' || ch == L']' || ch == L'(' || ch == L')' || ch == L'{' || ch == L'}';
}

/**
* Find if there is a brace next to the caret, checking before caret first, then
* after caret. If brace found also find its matching brace.
* @return @c true if inside a bracket pair.
*/
bool SciTEBase::FindMatchingBracePosition(bool editor, int &braceAtCaret, int &braceOpposite, bool sloppy) {
    int maskStyle = (1 << SendEditor(SCI_GETSTYLEBITSNEEDED)) - 1;
    bool isInside = false;
    Window &win = editor ? wEditor : wOutput;

	int mainSel = Platform::SendScintilla(win.GetID(), SCI_GETMAINSELECTION, 0, 0);
	if (Platform::SendScintilla(win.GetID(), SCI_GETSELECTIONNCARETVIRTUALSPACE, mainSel, 0) > 0)
		return false;

    int bracesStyleCheck = editor ? bracesStyle : 0;
    int caretPos = Platform::SendScintilla(win.GetID(), SCI_GETCURRENTPOS, 0, 0);
    braceAtCaret = -1;
    braceOpposite = -1;
    WCHAR charBefore = L'\0';
    WCHAR styleBefore = L'\0';
    int lengthDoc = Platform::SendScintilla(win.GetID(), SCI_GETLENGTH, 0, 0);
    WindowAccessor acc(win.GetID(), props);
    if ((lengthDoc > 0) && (caretPos > 0)) {
        // Check to ensure not matching brace that is part of a multibyte character
        if (Platform::SendScintilla(win.GetID(), SCI_POSITIONBEFORE, caretPos) == (caretPos - 1)) {
            charBefore = acc[caretPos - 1];
            styleBefore = static_cast<WCHAR>(acc.StyleAt(caretPos - 1) & maskStyle);
        }
    }
    // Priority goes to character before caret
    if (charBefore && IsBrace(charBefore) &&
        ((styleBefore == bracesStyleCheck) || (!bracesStyle))) {
            braceAtCaret = caretPos - 1;
    }
    bool colonMode = false;
    if ((lexLanguage == SCLEX_PYTHON) &&
        (':' == charBefore) && (SCE_P_OPERATOR == styleBefore)) {
            braceAtCaret = caretPos - 1;
            colonMode = true;
    }
    bool isAfter = true;
    if (lengthDoc > 0 && sloppy && (braceAtCaret < 0) && (caretPos < lengthDoc)) {
        // No brace found so check other side
        // Check to ensure not matching brace that is part of a multibyte character
        if (Platform::SendScintilla(win.GetID(), SCI_POSITIONAFTER, caretPos) == (caretPos + 1)) {
            WCHAR charAfter = acc[caretPos];
            WCHAR styleAfter = static_cast<WCHAR>(acc.StyleAt(caretPos) & maskStyle);
            if (charAfter && IsBrace(charAfter) && ((styleAfter == bracesStyleCheck) || (!bracesStyle))) {
                braceAtCaret = caretPos;
                isAfter = false;
            }
            if ((lexLanguage == SCLEX_PYTHON) &&
                (':' == charAfter) && (SCE_P_OPERATOR == styleAfter)) {
                    braceAtCaret = caretPos;
                    colonMode = true;
            }
        }
    }
    if (braceAtCaret >= 0) {
        if (colonMode) {
            int lineStart = Platform::SendScintilla(win.GetID(), SCI_LINEFROMPOSITION, braceAtCaret);
            int lineMaxSubord = Platform::SendScintilla(win.GetID(), SCI_GETLASTCHILD, lineStart, -1);
            braceOpposite = Platform::SendScintilla(win.GetID(), SCI_GETLINEENDPOSITION, lineMaxSubord);
        } else {
            braceOpposite = Platform::SendScintilla(win.GetID(), SCI_BRACEMATCH, braceAtCaret, 0);
        }
        if (braceOpposite > braceAtCaret) {
            isInside = isAfter;
        } else {
            isInside = !isAfter;
        }
    }
    return isInside;
}

void SciTEBase::BraceMatch(bool editor) {
    if (!bracesCheck)
        return;
    int braceAtCaret = -1;
    int braceOpposite = -1;
    FindMatchingBracePosition(editor, braceAtCaret, braceOpposite, bracesSloppy);
    Window &win = editor ? wEditor : wOutput;
    if ((braceAtCaret != -1) && (braceOpposite == -1)) {
        Platform::SendScintilla(win.GetID(), SCI_BRACEBADLIGHT, braceAtCaret, 0);
        SendEditor(SCI_SETHIGHLIGHTGUIDE, 0);
    } else {
        WCHAR chBrace = 0;
        if (braceAtCaret >= 0)
            chBrace = static_cast<WCHAR>(Platform::SendScintilla(
            win.GetID(), SCI_GETCHARAT, braceAtCaret, 0));
        Platform::SendScintilla(win.GetID(), SCI_BRACEHIGHLIGHT, braceAtCaret, braceOpposite);
        int columnAtCaret = Platform::SendScintilla(win.GetID(), SCI_GETCOLUMN, braceAtCaret, 0);
        int columnOpposite = Platform::SendScintilla(win.GetID(), SCI_GETCOLUMN, braceOpposite, 0);
        if (chBrace == ':') {
            int lineStart = Platform::SendScintilla(win.GetID(), SCI_LINEFROMPOSITION, braceAtCaret);
            int indentPos = Platform::SendScintilla(win.GetID(), SCI_GETLINEINDENTPOSITION, lineStart, 0);
            int indentPosNext = Platform::SendScintilla(win.GetID(), SCI_GETLINEINDENTPOSITION, lineStart + 1, 0);
            columnAtCaret = Platform::SendScintilla(win.GetID(), SCI_GETCOLUMN, indentPos, 0);
            int columnAtCaretNext = Platform::SendScintilla(win.GetID(), SCI_GETCOLUMN, indentPosNext, 0);
            int indentSize = Platform::SendScintilla(win.GetID(), SCI_GETINDENT);
            if (columnAtCaretNext - indentSize > 1)
                columnAtCaret = columnAtCaretNext - indentSize;
            //Platform::DebugPrintf(": %d %d %d\n", lineStart, indentPos, columnAtCaret);
            if (columnOpposite == 0)	// If the final line of the structure is empty
                columnOpposite = columnAtCaret;
        }

        if (props.GetInt(L"highlight.indentation.guides"))
            Platform::SendScintilla(win.GetID(), SCI_SETHIGHLIGHTGUIDE, Platform::Minimum(columnAtCaret, columnOpposite), 0);
    }
}

void SciTEBase::SetWindowName() {
    if (filePath.IsUntitled()) {
        windowName = localiser.Text(L"Untitled");
        windowName.insert(0, L"(");
        windowName += L")";
    } else if (props.GetInt(L"title.full.path") == 2) {
        windowName = FileNameExt().AsInternal();
        windowName += L" in ";
        windowName += filePath.Directory().AsInternal();
    } else if (props.GetInt(L"title.full.path") == 1) {
        windowName = filePath.AsInternal();
    } else {
        windowName = FileNameExt().AsInternal();
    }
    if (CurrentBuffer()->isDirty)
        windowName += L" * ";
    else
        windowName += L" - ";
    windowName += appName;

    if (buffers.length > 1 && props.GetInt(L"title.show.buffers")) {
        windowName += L" [";
        windowName += SString(buffers.Current() + 1);
        windowName += L" of ";
        windowName += SString(buffers.length);
        windowName += L"]";
    }

    wSciTE.SetTitle(windowName.c_str());
}

Sci_CharacterRange SciTEBase::GetSelection() {
    Sci_CharacterRange crange;
    crange.cpMin = SendEditor(SCI_GETSELECTIONSTART);
    crange.cpMax = SendEditor(SCI_GETSELECTIONEND);
    return crange;
}

void SciTEBase::SetSelection(int anchor, int currentPos) {
    SendEditor(SCI_SETSEL, anchor, currentPos);
}

void SciTEBase::GetCTag(WCHAR *sel, int len) {
    int lengthDoc, selStart, selEnd;
    int mustStop = 0;
    WCHAR c;
    Window wCurrent;

    if (wEditor.HasFocus()) {
        wCurrent = wEditor;
    } else {
        wCurrent = wOutput;
    }
    lengthDoc = SendFocused(SCI_GETLENGTH);
    selStart = selEnd = SendFocused(SCI_GETSELECTIONEND);
    WindowAccessor acc(wCurrent.GetID(), props);
    while (!mustStop) {
        if (selStart < lengthDoc - 1) {
            selStart++;
            c = acc[selStart];
            if (c == L'\r' || c == L'\n') {
                mustStop = -1;
            } else if (c == L'\t' && ((acc[selStart + 1] == L'/' && acc[selStart + 2] == L'^') || isdigit(acc[selStart + 1]))) {
                mustStop = 1;
            }
        } else {
            mustStop = -1;
        }
    }
    if (mustStop == 1 && (acc[selStart + 1] == L'/' && acc[selStart + 2] == L'^')) {	// Found
        selEnd = selStart += 3;
        mustStop = 0;
        while (!mustStop) {
            if (selEnd < lengthDoc - 1) {
                selEnd++;
                c = acc[selEnd];
                if (c == L'\r' || c == L'\n') {
                    mustStop = -1;
                } else if (c == L'$' && acc[selEnd + 1] == L'/') {
                    mustStop = 1;	// Found!
                }

            } else {
                mustStop = -1;
            }
        }
    } else if (mustStop == 1 && isdigit(acc[selStart + 1])) {
        // a Tag can be referenced by line Number also
        selEnd = selStart += 1;
        while (isdigit(acc[selEnd]) && (selEnd < lengthDoc)) {
            selEnd++;
        }
    }

    sel[0] = L'\0';
    if ((selStart < selEnd) && ((selEnd - selStart + 1) < len)) {
        GetRange(wCurrent, selStart, selEnd, sel);
    }
}

// Default characters that can appear in a word
bool SciTEBase::iswordcharforsel(WCHAR ch) {
    return !wcschr(L"\t\n\r !\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", ch);
}

// Accept slightly more characters than for a word
// Doesn't accept all valid characters, as they are rarely used in source filenames...
// Accept path separators '/' and '\', extension separator '.', and ':', MS drive unit
// separator, and also used for separating the line number for grep. Same for '(' and ')' for cl.
bool SciTEBase::isfilenamecharforsel(WCHAR ch) {
    return !wcschr(L"\t\n\r \"$%'*,;<>?[]^`{|}", ch);
}

bool SciTEBase::islexerwordcharforsel(WCHAR ch) {
    // If there are no word.characters defined for the current file, fall back on the original function
    if (wordCharacters.length())
        return wordCharacters.contains(ch);
    else
        return iswordcharforsel(ch);
}

SString SciTEBase::GetRange(Window &win, int selStart, int selEnd) {
    SBuffer sel(selEnd - selStart);
    Sci_TextRange tr;
    tr.chrg.cpMin = selStart;
    tr.chrg.cpMax = selEnd;
    tr.lpstrText = sel.ptr();
    Platform::SendScintillaPointer(win.GetID(), SCI_GETTEXTRANGE, 0, &tr);
    return SString(sel);
}

SString SciTEBase::GetRangeInUIEncoding(Window &win, int selStart, int selEnd) {
    return GetRange(win, selStart, selEnd);
}

SString SciTEBase::GetLine(Window &win, int line) {
    int lineStart = SendWindow(win, SCI_POSITIONFROMLINE, line);
    int lineEnd = SendWindow(win, SCI_GETLINEENDPOSITION, line);
    return GetRange(win, lineStart, lineEnd);
}

SString SciTEBase::RangeExtendAndGrab(
                                      Window &wCurrent,
                                      int &selStart,
                                      int &selEnd,
                                      bool (SciTEBase::*ischarforsel)(WCHAR ch),	///< Function returning @c true if the given char. is part of the selection.
                                      bool stripEol /*=true*/) {

                                          if (selStart == selEnd && ischarforsel) {
                                              // Empty range and have a function to extend it
                                              int lengthDoc = SendWindow(wCurrent, SCI_GETLENGTH);
                                              WindowAccessor acc(wCurrent.GetID(), props);
                                              // Try and find a word at the caret
                                              // On the left...
                                              while ((selStart > 0) && ((this->*ischarforsel)(acc[selStart - 1]))) {
                                                  selStart--;
                                              }
                                              // and on the right
                                              while ((selEnd < lengthDoc) && ((this->*ischarforsel)(acc[selEnd]))) {
                                                  selEnd++;
                                              }
                                          }
                                          SString selected;
                                          if (selStart != selEnd) {
                                              selected = GetRangeInUIEncoding(wCurrent, selStart, selEnd);
                                          }
                                          if (stripEol) {
                                              // Change whole line selected but normally end of line characters not wanted.
                                              // Remove possible terminating \r, \n, or \r\n.
                                              size_t sellen = selected.length();
                                              if (sellen >= 2 && (selected[sellen - 2] == '\r' && selected[sellen - 1] == '\n')) {
                                                  selected.remove(sellen - 2, 0);
                                              } else if (sellen >= 1 && (selected[sellen - 1] == '\r' || selected[sellen - 1] == '\n')) {
                                                  selected.remove(sellen - 1, 0);
                                              }
                                          }

                                          return selected;
}

/**
* If there is selected text, either in the editor or the output pane,
* put the selection in the @a sel buffer, up to @a len characters.
* Otherwise, try and select characters around the caret, as long as they are OK
* for the @a ischarforsel function.
* Remove the last two character controls from the result, as they are likely
* to be CR and/or LF.
*/
SString SciTEBase::SelectionExtend(
                                   bool (SciTEBase::*ischarforsel)(WCHAR ch),	///< Function returning @c true if the given char. is part of the selection.
                                   bool stripEol /*=true*/) {

                                       Window wCurrent;

                                       if (wOutput.HasFocus()) {
                                           wCurrent = wOutput;
                                       } else {
                                           wCurrent = wEditor;
                                       }
                                       int selStart = SendFocused(SCI_GETSELECTIONSTART);
                                       int selEnd = SendFocused(SCI_GETSELECTIONEND);
                                       return RangeExtendAndGrab(wCurrent, selStart, selEnd, ischarforsel, stripEol);
}

void SciTEBase::FindWordAtCaret(int &start, int &end) {
    Window wCurrent;

    if (wOutput.HasFocus()) {
        wCurrent = wOutput;
    } else {
        wCurrent = wEditor;
    }
    start = SendFocused(SCI_GETSELECTIONSTART);
    end = SendFocused(SCI_GETSELECTIONEND);
    // Call just to update start & end
    RangeExtendAndGrab(wCurrent, start, end, &SciTEBase::iswordcharforsel, false);
}

bool SciTEBase::SelectWordAtCaret() {
    int selStart = 0;
    int selEnd = 0;
    FindWordAtCaret(selStart, selEnd);
    SetSelection(selStart, selEnd);
    return selStart != selEnd;
}

SString SciTEBase::SelectionWord(bool stripEol /*=true*/) {
    return SelectionExtend(&SciTEBase::islexerwordcharforsel, stripEol);
}

SString SciTEBase::SelectionFilename() {
    return SelectionExtend(&SciTEBase::isfilenamecharforsel);
}

void SciTEBase::SelectionIntoProperties() {
    SString currentSelection = SelectionExtend(0, false);
    props.Set(L"CurrentSelection", currentSelection.c_str());

    SString word = SelectionWord();
    props.Set(L"CurrentWord", word.c_str());

    int selStart = SendFocused(SCI_GETSELECTIONSTART);
    int selEnd = SendFocused(SCI_GETSELECTIONEND);
    props.SetInteger(L"SelectionStartLine", SendFocused(SCI_LINEFROMPOSITION, selStart) + 1);
    props.SetInteger(L"SelectionStartColumn", SendFocused(SCI_GETCOLUMN, selStart) + 1);
    props.SetInteger(L"SelectionEndLine", SendFocused(SCI_LINEFROMPOSITION, selEnd) + 1);
    props.SetInteger(L"SelectionEndColumn", SendFocused(SCI_GETCOLUMN, selEnd) + 1);
}

void SciTEBase::SelectionIntoFind(bool stripEol /*=true*/) {
    SString sel = SelectionWord(stripEol);
    if (sel.length() && !sel.contains('\r') && !sel.contains('\n')) {
        // The selection does not include a new line, so is likely to be
        // the expression to search...
        findWhat = sel;
        if (unSlash) {
            WCHAR *slashedFind = Slash(findWhat.c_str(), false);
            if (slashedFind) {
                findWhat = slashedFind;
                delete []slashedFind;
            }
        }
    }
    // else findWhat remains the same as last time.
}

SString SciTEBase::EncodeString(const SString &s) {
    return SString(s);
}

/**
* Convert a string into C string literal form using \a, \b, \f, \n, \r, \t, \v, and \ooo.
* The return value is a newly allocated character array containing the result.
* 4 bytes are allocated for each byte of the input because that is the maximum
* expansion needed when all of the input needs to be output using the octal form.
* The return value should be deleted with delete[].
*/
WCHAR *Slash(const WCHAR *s, bool quoteQuotes) {
    WCHAR *oRet = new WCHAR[wcslen(s) * 4 + 1];
    WCHAR *o = oRet;
    while (*s) {
        if (*s == L'\a') {
            *o++ = L'\\';
            *o++ = L'a';
        } else if (*s == L'\b') {
            *o++ = L'\\';
            *o++ = L'b';
        } else if (*s == L'\f') {
            *o++ = L'\\';
            *o++ = L'f';
        } else if (*s == L'\n') {
            *o++ = L'\\';
            *o++ = L'n';
        } else if (*s == L'\r') {
            *o++ = L'\\';
            *o++ = L'r';
        } else if (*s == L'\t') {
            *o++ = L'\\';
            *o++ = L't';
        } else if (*s == L'\v') {
            *o++ = L'\\';
            *o++ = L'v';
        } else if (*s == L'\\') {
            *o++ = L'\\';
            *o++ = L'\\';
        } else if (quoteQuotes && (*s == L'\'')) {
            *o++ = L'\\';
            *o++ = L'\'';
        } else if (quoteQuotes && (*s == L'\"')) {
            *o++ = L'\\';
            *o++ = L'\"';
        } else if (isascii(*s) && (*s < L' ')) {
            *o++ = L'\\';
            *o++ = static_cast<WCHAR>((*s >> 6) + L'0');
            *o++ = static_cast<WCHAR>((*s >> 3) + L'0');
            *o++ = static_cast<WCHAR>((*s & 0x7) + L'0');
        } else {
            *o++ = *s;
        }
        s++;
    }
    *o = L'\0';
    return oRet;
}

/**
* Is the character an octal digit?
*/
static bool IsOctalDigit(WCHAR ch) {
    return ch >= L'0' && ch <= L'7';
}

/**
* If the character is an hexa digit, get its value.
*/
static int GetHexaDigit(WCHAR ch) {
    if (ch >= L'0' && ch <= L'9') {
        return ch - L'0';
    }
    if (ch >= L'A' && ch <= L'F') {
        return ch - L'A' + 10;
    }
    if (ch >= L'a' && ch <= L'f') {
        return ch - L'a' + 10;
    }
    return -1;
}

/**
* Convert C style \a, \b, \f, \n, \r, \t, \v, \ooo and \xhh into their indicated characters.
*/
unsigned int UnSlash(WCHAR *s) {
    WCHAR *sStart = s;
    WCHAR *o = s;

    while (*s) {
        if (*s == L'\\') {
            s++;
            if (*s == L'a') {
                *o = L'\a';
            } else if (*s == L'b') {
                *o = L'\b';
            } else if (*s == L'f') {
                *o = L'\f';
            } else if (*s == L'n') {
                *o = L'\n';
            } else if (*s == L'r') {
                *o = L'\r';
            } else if (*s == L't') {
                *o = L'\t';
            } else if (*s == L'v') {
                *o = L'\v';
            } else if (IsOctalDigit(*s)) {
                int val = *s - L'0';
                if (IsOctalDigit(*(s + 1))) {
                    s++;
                    val *= 8;
                    val += *s - L'0';
                    if (IsOctalDigit(*(s + 1))) {
                        s++;
                        val *= 8;
                        val += *s - L'0';
                    }
                }
                *o = static_cast<WCHAR>(val);
            } else if (*s == L'x') {
                s++;
                int val = 0;
                int ghd = GetHexaDigit(*s);
                if (ghd >= 0) {
                    s++;
                    val = ghd;
                    ghd = GetHexaDigit(*s);
                    if (ghd >= 0) {
                        s++;
                        val *= 16;
                        val += ghd;
                    }
                }
                *o = static_cast<WCHAR>(val);
            } else {
                *o = *s;
            }
        } else {
            *o = *s;
        }
        o++;
        if (*s) {
            s++;
        }
    }
    *o = L'\0';
    return o - sStart;
}

/**
* Convert C style \0oo into their indicated characters.
* This is used to get control characters into the regular expresion engine.
*/
unsigned int UnSlashLowOctal(WCHAR *s) {
    WCHAR *sStart = s;
    WCHAR *o = s;
    while (*s) {
        if ((s[0] == L'\\') && (s[1] == L'0') && IsOctalDigit(s[2]) && IsOctalDigit(s[3])) {
            *o = static_cast<WCHAR>(8 * (s[2] - L'0') + (s[3] - L'0'));
            s += 3;
        } else {
            *o = *s;
        }
        o++;
        if (*s)
            s++;
    }
    *o = L'\0';
    return o - sStart;
}

static int UnSlashAsNeeded(SString &s, bool escapes, bool regularExpression) {
    if (escapes) {
        WCHAR *sUnslashed = StringDup(s.c_str(), s.length());
        size_t len;
        if (regularExpression) {
            // For regular expressions, the only escape sequences allowed start with \0
            // Other sequences, like \t, are handled by the RE engine.
            len = UnSlashLowOctal(sUnslashed);
        } else {
            // C style escapes allowed
            len = UnSlash(sUnslashed);
        }
        s = sUnslashed;
        delete []sUnslashed;
        return static_cast<int>(len);
    } else {
        return s.length();
    }
}

void SciTEBase::RemoveFindMarks() {
    if (CurrentBuffer()->findMarks != Buffer::fmNone) {
        SendEditor(SCI_SETINDICATORCURRENT, indicatorMatch);
        SendEditor(SCI_INDICATORCLEARRANGE, 0, LengthDocument());
        CurrentBuffer()->findMarks = Buffer::fmNone;
    }
}

int SciTEBase::MarkAll() {
    int posCurrent = SendEditor(SCI_GETCURRENTPOS);
    int marked = 0;
    int posFirstFound = FindNext(false, false);

    SString findMark = props.Get(L"find.mark");
    if (findMark.length()) {
        SendEditor(SCI_SETINDICATORCURRENT, indicatorMatch);
        RemoveFindMarks();
        CurrentBuffer()->findMarks = Buffer::fmMarked;
    }
    if (posFirstFound != -1) {
        int posFound = posFirstFound;
        do {
            marked++;
            int line = SendEditor(SCI_LINEFROMPOSITION, posFound);
            BookmarkAdd(line);
            if (findMark.length()) {
                SendEditor(SCI_INDICATORFILLRANGE, posFound, SendEditor(SCI_GETTARGETEND) - posFound);
            }
            posFound = FindNext(false, false);
        } while ((posFound != -1) && (posFound != posFirstFound));
    }
    SendEditor(SCI_SETCURRENTPOS, posCurrent);
    return marked;
}

int SciTEBase::IncrementSearchMode() {
    FindIncrement();
    return 0;
}

int SciTEBase::FindInTarget(const WCHAR *findWhat, int lenFind, int startPosition, int endPosition) {
    SendEditor(SCI_SETTARGETSTART, startPosition);
    SendEditor(SCI_SETTARGETEND, endPosition);
    int posFind = SendEditorString(SCI_SEARCHINTARGET, lenFind, findWhat);
    while (findInStyle && posFind != -1 && findStyle != SendEditor(SCI_GETSTYLEAT, posFind)) {
        if (startPosition < endPosition) {
            SendEditor(SCI_SETTARGETSTART, posFind + 1);
            SendEditor(SCI_SETTARGETEND, endPosition);
        } else {
            SendEditor(SCI_SETTARGETSTART, startPosition);
            SendEditor(SCI_SETTARGETEND, posFind + 1);
        }
        posFind = SendEditorString(SCI_SEARCHINTARGET, lenFind, findWhat);
    }
    return posFind;
}

int SciTEBase::FindNext(bool reverseDirection, bool showWarnings) {
    if (findWhat.length() == 0) {
        Find();
        return -1;
    }
    SString findTarget = EncodeString(findWhat);
    int lenFind = UnSlashAsNeeded(findTarget, unSlash, regExp);
    if (lenFind == 0)
        return -1;

    Sci_CharacterRange cr = GetSelection();
    int startPosition = cr.cpMax;
    int endPosition = LengthDocument();
    if (reverseDirection) {
        startPosition = cr.cpMin;
        endPosition = 0;
    }

    int flags = (wholeWord ? SCFIND_WHOLEWORD : 0) |
        (matchCase ? SCFIND_MATCHCASE : 0) |
        (regExp ? SCFIND_REGEXP : 0) |
        (props.GetInt(L"find.replace.regexp.posix") ? SCFIND_POSIX : 0);

    SendEditor(SCI_SETSEARCHFLAGS, flags);
    int posFind = FindInTarget(findTarget.c_str(), lenFind, startPosition, endPosition);
    if (posFind == -1 && wrapFind) {
        // Failed to find in indicated direction
        // so search from the beginning (forward) or from the end (reverse)
        // unless wrapFind is false
        if (reverseDirection) {
            startPosition = LengthDocument();
            endPosition = 0;
        } else {
            startPosition = 0;
            endPosition = LengthDocument();
        }
        posFind = FindInTarget(findTarget.c_str(), lenFind, startPosition, endPosition);
        WarnUser(warnFindWrapped);
    }
    if (posFind == -1) {
        havefound = false;
        if (showWarnings) {
            WarnUser(warnNotFound);
            FindMessageBox(L"Can not find the string '^0'.",
                &findWhat);
        }
    } else {
        havefound = true;
        int start = SendEditor(SCI_GETTARGETSTART);
        int end = SendEditor(SCI_GETTARGETEND);
        EnsureRangeVisible(start, end);
        SetSelection(start, end);
        if (!replacing) {
            DestroyFindReplace();
        }
    }
    return posFind;
}

void SciTEBase::ReplaceOnce() {
    if (havefound) {
        SString replaceTarget = EncodeString(replaceWhat);
        int replaceLen = UnSlashAsNeeded(replaceTarget, unSlash, regExp);
        Sci_CharacterRange cr = GetSelection();
        SendEditor(SCI_SETTARGETSTART, cr.cpMin);
        SendEditor(SCI_SETTARGETEND, cr.cpMax);
        int lenReplaced = replaceLen;
        if (regExp)
            lenReplaced = SendEditorString(SCI_REPLACETARGETRE, replaceLen, replaceTarget.c_str());
        else	// Allow \0 in replacement
            SendEditorString(SCI_REPLACETARGET, replaceLen, replaceTarget.c_str());
        SetSelection(cr.cpMin + lenReplaced, cr.cpMin);
        havefound = false;
    }

    FindNext(false);
}

int SciTEBase::DoReplaceAll(bool inSelection) {
    SString findTarget = EncodeString(findWhat);
    int findLen = UnSlashAsNeeded(findTarget, unSlash, regExp);
    if (findLen == 0) {
        return -1;
    }

    Sci_CharacterRange cr = GetSelection();
    int startPosition = cr.cpMin;
    int endPosition = cr.cpMax;
	int countSelections = SendEditor(SCI_GETSELECTIONS);
    if (inSelection) {
		int selType = SendEditor(SCI_GETSELECTIONMODE);
		if (selType == SC_SEL_LINES) {
			// Take care to replace in whole lines
			int startLine = SendEditor(SCI_LINEFROMPOSITION, startPosition);
			startPosition = SendEditor(SCI_POSITIONFROMLINE, startLine);
			int endLine = SendEditor(SCI_LINEFROMPOSITION, endPosition);
			endPosition = SendEditor(SCI_POSITIONFROMLINE, endLine + 1);
		} else {
			for (int i=0; i<countSelections; i++) {
				startPosition = Platform::Minimum(startPosition, SendEditor(SCI_GETSELECTIONNSTART, i));
				endPosition = Platform::Maximum(endPosition, SendEditor(SCI_GETSELECTIONNEND, i));
			}
		}
		if (startPosition == endPosition) {
			return -2;
		}
    } else {
        endPosition = LengthDocument();
        if (wrapFind) {
            // Whole document
            startPosition = 0;
        }
        // If not wrapFind, replace all only from caret to end of document
    }

    SString replaceTarget = EncodeString(replaceWhat);
    int replaceLen = UnSlashAsNeeded(replaceTarget, unSlash, regExp);
    int flags = (wholeWord ? SCFIND_WHOLEWORD : 0) |
        (matchCase ? SCFIND_MATCHCASE : 0) |
        (regExp ? SCFIND_REGEXP : 0) |
        (props.GetInt(L"find.replace.regexp.posix") ? SCFIND_POSIX : 0);
    SendEditor(SCI_SETSEARCHFLAGS, flags);
    int posFind = FindInTarget(findTarget.c_str(), findLen, startPosition, endPosition);
    if ((findLen == 1) && regExp && (findTarget[0] == '^')) {
        // Special case for replace all start of line so it hits the first line
        posFind = startPosition;
        SendEditor(SCI_SETTARGETSTART, startPosition);
        SendEditor(SCI_SETTARGETEND, startPosition);
    }
    if ((posFind != -1) && (posFind <= endPosition)) {
        int lastMatch = posFind;
        int replacements = 0;
        SendEditor(SCI_BEGINUNDOACTION);
        // Replacement loop
        while (posFind != -1) {
            int lenTarget = SendEditor(SCI_GETTARGETEND) - SendEditor(SCI_GETTARGETSTART);
			if (inSelection && countSelections > 1) {
				// We must check that the found target is entirely inside a selection
				bool insideASelection = false;
				for (int i=0; i<countSelections && !insideASelection; i++) {
					int startPos= SendEditor(SCI_GETSELECTIONNSTART, i);
					int endPos = SendEditor(SCI_GETSELECTIONNEND, i);
					if (posFind >= startPos && posFind + lenTarget <= endPos)
						insideASelection = true;
				}
				if (!insideASelection) {
					// Found target is totally or partly outside the selections
                        lastMatch = posFind + 1;
                        if (lastMatch >= endPosition) {
                            // Run off the end of the document/selection with an empty match
                            posFind = -1;
                        } else {
                            posFind = FindInTarget(findTarget.c_str(), findLen, lastMatch, endPosition);
                        }
                        continue;	// No replacement
                }
            }
            int movepastEOL = 0;
            if (lenTarget <= 0) {
                WCHAR chNext = static_cast<WCHAR>(SendEditor(SCI_GETCHARAT, SendEditor(SCI_GETTARGETEND)));
                if (chNext == L'\r' || chNext == L'\n') {
                    movepastEOL = 1;
                }
            }
            int lenReplaced = replaceLen;
            if (regExp) {
                lenReplaced = SendEditorString(SCI_REPLACETARGETRE, replaceLen, replaceTarget.c_str());
            } else {
                SendEditorString(SCI_REPLACETARGET, replaceLen, replaceTarget.c_str());
            }
            // Modify for change caused by replacement
            endPosition += lenReplaced - lenTarget;
            // For the special cases of start of line and end of line
            // something better could be done but there are too many special cases
            lastMatch = posFind + lenReplaced + movepastEOL;
            if (lenTarget == 0) {
                lastMatch = SendEditor(SCI_POSITIONAFTER, lastMatch);
            }
            if (lastMatch >= endPosition) {
                // Run off the end of the document/selection with an empty match
                posFind = -1;
            } else {
                posFind = FindInTarget(findTarget.c_str(), findLen, lastMatch, endPosition);
            }
            replacements++;
        }
        if (inSelection) {
			if (countSelections == 1) 
            SetSelection(startPosition, endPosition);
        } else {
            SetSelection(lastMatch, lastMatch);
        }
        SendEditor(SCI_ENDUNDOACTION);
        return replacements;
    }
    return 0;
    //Platform::DebugPrintf("ReplaceAll <%s> -> <%s>\n", findWhat, replaceWhat);
}

int SciTEBase::ReplaceAll(bool inSelection) {
    int replacements = DoReplaceAll(inSelection);
    props.SetInteger(L"Replacements", (replacements > 0 ? replacements : 0));
    UpdateStatusBar(false);
    if (replacements == -1) {
        FindMessageBox(
            inSelection ?
            L"Find string must not be empty for 'Replace in Selection' command." :
        L"Find string must not be empty for 'Replace All' command.");
    } else if (replacements == -2) {
        FindMessageBox(
            L"Selection must not be empty for 'Replace in Selection' command.");
    } else if (replacements == 0) {
        FindMessageBox(
            L"No replacements because string '^0' was not present.", &findWhat);
    }
    return replacements;
}

int SciTEBase::ReplaceInBuffers() {
    int currentBuffer = buffers.Current();
    int replacements = 0;
    for (int i = 0; i < buffers.length; i++) {
        SetDocumentAt(i);
        replacements += DoReplaceAll(false);
        if (i == 0 && replacements < 0) {
            FindMessageBox(
                L"Find string must not be empty for 'Replace in Buffers' command.");
            break;
        }
    }
    SetDocumentAt(currentBuffer);
    props.SetInteger(L"Replacements", replacements);
    UpdateStatusBar(false);
    if (replacements == 0) {
        FindMessageBox(
            L"No replacements because string '^0' was not present.", &findWhat);
    }
    return replacements;
}

void SciTEBase::OutputAppendString(const WCHAR *s, int len) {
    if (len == -1)
        len = static_cast<int>(wcslen(s));
    SendOutput(SCI_APPENDTEXT, len, reinterpret_cast<sptr_t>(s));
    if (scrollOutput) {
        int line = SendOutput(SCI_GETLINECOUNT, 0, 0);
        int lineStart = SendOutput(SCI_POSITIONFROMLINE, line);
        SendOutput(SCI_GOTOPOS, lineStart);
    }
}

void SciTEBase::OutputAppendStringSynchronised(const WCHAR *s, int len) {
    if (len == -1)
        len = static_cast<int>(wcslen(s));
    SendOutputEx(SCI_APPENDTEXT, len, reinterpret_cast<sptr_t>(s), false);
    if (scrollOutput) {
        int line = SendOutputEx(SCI_GETLINECOUNT, 0, 0, false);
        int lineStart = SendOutputEx(SCI_POSITIONFROMLINE, line, 0, false);
        SendOutputEx(SCI_GOTOPOS, lineStart, 0, false);
    }
}

void SciTEBase::MakeOutputVisible() {
    if (heightOutput <= 0) {
        ToggleOutputVisible();
    }
}

void SciTEBase::ClearJobQueue() {
    for (int ic = 0; ic < jobQueue.commandMax; ic++) {
        jobQueue.jobQueue[ic].Clear();
    }
    jobQueue.commandCurrent = 0;
}

void SciTEBase::Execute() {
    props.Set(L"CurrentMessage", L"");
    dirNameForExecute = FilePath();
    bool displayParameterDialog = false;
    int ic;
    parameterisedCommand = L"";
    for (ic = 0; ic < jobQueue.commandMax; ic++) {
        if (jobQueue.jobQueue[ic].command[0] == L'*') {
            displayParameterDialog = true;
            jobQueue.jobQueue[ic].command.remove(0, 1);
            parameterisedCommand = jobQueue.jobQueue[ic].command;
        }
        if (jobQueue.jobQueue[ic].directory.IsSet()) {
            dirNameForExecute = jobQueue.jobQueue[ic].directory;
        }
    }
    if (displayParameterDialog) {
        if (!ParametersDialog(true)) {
            ClearJobQueue();
            return;
        }
    } else {
        ParamGrab();
    }
    for (ic = 0; ic < jobQueue.commandMax; ic++) {
        jobQueue.jobQueue[ic].command = props.Expand(jobQueue.jobQueue[ic].command.c_str());
    }

    if (jobQueue.ClearBeforeExecute()) {
        SendOutputEx(SCI_CLEARALL, 0, 0, false);
    }

    SendOutput(SCI_MARKERDELETEALL, static_cast<uptr_t>(-1));
    SendEditor(SCI_MARKERDELETEALL, 0);
    // Ensure the output pane is visible
    if (jobQueue.ShowOutputPane()) {
        MakeOutputVisible();
    }

    jobQueue.cancelFlag = 0L;
    jobQueue.SetExecuting(true);
    CheckMenus();
    filePath.Directory().SetWorkingDirectory();
    dirNameAtExecute = filePath.Directory();
}

void SciTEBase::ToggleOutputVisible() {
    if (heightOutput > 0) {
        heightOutput = NormaliseSplit(0);
        WindowSetFocus(wEditor);
    } else {
        if (previousHeightOutput < 20) {
            if (splitVertical)
                heightOutput = NormaliseSplit(300);
            else
                heightOutput = NormaliseSplit(100);
            previousHeightOutput = heightOutput;
        } else {
            heightOutput = NormaliseSplit(previousHeightOutput);
        }
    }
    SizeSubWindows();
    Redraw();
}

void SciTEBase::BookmarkAdd(int lineno) {
    if (lineno == -1)
        lineno = GetCurrentLineNumber();
    if (!BookmarkPresent(lineno))
        SendEditor(SCI_MARKERADD, lineno, markerBookmark);
}

void SciTEBase::BookmarkDelete(int lineno) {
    if (lineno == -1)
        lineno = GetCurrentLineNumber();
    if (BookmarkPresent(lineno))
        SendEditor(SCI_MARKERDELETE, lineno, markerBookmark);
}

bool SciTEBase::BookmarkPresent(int lineno) {
    if (lineno == -1)
        lineno = GetCurrentLineNumber();
    int state = SendEditor(SCI_MARKERGET, lineno);
    return state & (1 << markerBookmark);
}

void SciTEBase::BookmarkToggle(int lineno) {
    if (lineno == -1)
        lineno = GetCurrentLineNumber();
    if (BookmarkPresent(lineno)) {
        BookmarkDelete(lineno);
    } else {
        BookmarkAdd(lineno);
    }
}

void SciTEBase::BookmarkNext(bool forwardScan, bool select) {
    int lineno = GetCurrentLineNumber();
    int sci_marker = SCI_MARKERNEXT;
    int lineStart = lineno + 1;	//Scan starting from next line
    int lineRetry = 0;				//If not found, try from the beginning
    int anchor = SendEditor(SCI_GETANCHOR);
    if (!forwardScan) {
        lineStart = lineno - 1;		//Scan starting from previous line
        lineRetry = SendEditor(SCI_GETLINECOUNT, 0, 0L);	//If not found, try from the end
        sci_marker = SCI_MARKERPREVIOUS;
    }
    int nextLine = SendEditor(sci_marker, lineStart, 1 << markerBookmark);
    if (nextLine < 0)
        nextLine = SendEditor(sci_marker, lineRetry, 1 << markerBookmark);
    if (nextLine < 0 || nextLine == lineno)	// No bookmark (of the given type) or only one, and already on it
        WarnUser(warnNoOtherBookmark);
    else {
        GotoLineEnsureVisible(nextLine);
        if (select) {
            SendEditor(SCI_SETANCHOR, anchor);
        }
    }
}

PRectangle SciTEBase::GetClientRectangle() {
    return wContent.GetClientPosition();
}

void SciTEBase::Redraw() {
    wSciTE.InvalidateAll();
    wEditor.InvalidateAll();
    wOutput.InvalidateAll();
}

WCHAR *SciTEBase::GetNearestWords(const WCHAR *wordStart, int searchLen,
                                 const WCHAR *separators, bool ignoreCase /*=false*/, bool exactLen /*=false*/) {
                                     WCHAR *words = 0;
                                     while (!words && *separators) {
                                         words = apis.GetNearestWords(wordStart, searchLen, ignoreCase, *separators, exactLen);
                                         separators++;
                                     }
                                     return words;
}

void SciTEBase::FillFunctionDefinition(int pos /*= -1*/) {
    if (pos > 0) {
        lastPosCallTip = pos;
    }
    if (apis) {
        WCHAR *words = GetNearestWords(currentCallTipWord.c_str(), currentCallTipWord.length(),
            calltipParametersStart.c_str(), callTipIgnoreCase, true);
        if (!words)
            return;
        // Counts how many call tips
        const WCHAR *spacePos = wcschr(words, L' ');
        maxCallTips = 1;
        while (spacePos) {
            maxCallTips++;
            spacePos = wcschr(spacePos + 1, L' ');
        }
        delete []words;

        // Should get current api definition
        const WCHAR *word = apis.GetNearestWord(currentCallTipWord.c_str(), currentCallTipWord.length(),
            callTipIgnoreCase, calltipWordCharacters, currentCallTip);
        if (word) {
            functionDefinition = word;
            if (maxCallTips > 1) {
                functionDefinition.insert(0, L"\001");
            }

            if (calltipEndDefinition != L"") {
                int posEndDef = functionDefinition.search(calltipEndDefinition.c_str());
                if (maxCallTips > 1) {
                    if ((posEndDef > 1) &&
                        ((posEndDef + calltipEndDefinition.length()) < functionDefinition.length())) {
                            functionDefinition.insert(posEndDef + calltipEndDefinition.length(), L"\n\002");
                    } else {
                        functionDefinition.append(L"\n\002");
                    }
                } else {
                    if ((posEndDef > 1) &&
                        ((posEndDef + calltipEndDefinition.length()) < functionDefinition.length())) {
                            functionDefinition.insert(posEndDef + calltipEndDefinition.length(), L"\n");
                    }
                }
            } else if (maxCallTips > 1) {
                functionDefinition.insert(1, L"\002");
            }
            SendEditorString(SCI_CALLTIPSHOW, lastPosCallTip - currentCallTipWord.length(), functionDefinition.c_str());
            ContinueCallTip();
        }
    }
}

bool SciTEBase::StartCallTip() {
    currentCallTip = 0;
    currentCallTipWord = L"";
    SString line = GetLine();
    int current = GetCaretInLine();
    int pos = SendEditor(SCI_GETCURRENTPOS);
    int braces;
    do {
        braces = 0;
        while (current > 0 && (braces || !calltipParametersStart.contains(line[current - 1]))) {
            if (calltipParametersStart.contains(line[current - 1]))
                braces--;
            else if (calltipParametersEnd.contains(line[current - 1]))
                braces++;
            current--;
            pos--;
        }
        if (current > 0) {
            current--;
            pos--;
        } else
            break;
        while (current > 0 && isspacechar(line[current - 1])) {
            current--;
            pos--;
        }
    } while (current > 0 && !calltipWordCharacters.contains(line[current - 1]));
    if (current <= 0)
        return true;

    startCalltipWord = current - 1;
    while (startCalltipWord > 0 &&
        calltipWordCharacters.contains(line[startCalltipWord - 1])) {
            startCalltipWord--;
    }

    line.change(current, L'\0');
    currentCallTipWord = line.c_str() + startCalltipWord;
    functionDefinition = L"";
    //Platform::DebugPrintf("word  is [%s] %d %d %d\n", currentCallTipWord.c_str(), currentCallTipWord.length(), pos, pos - rootlen);
    FillFunctionDefinition(pos);
    return true;
}

void SciTEBase::ContinueCallTip() {
    SString line = GetLine();
    int current = GetCaretInLine();

    int braces = 0;
    int commas = 0;
    for (int i = startCalltipWord; i < current; i++) {
        if (calltipParametersStart.contains(line[i]))
            braces++;
        else if (calltipParametersEnd.contains(line[i]) && braces > 0)
            braces--;
        else if (braces == 1 && calltipParametersSeparators.contains(line[i]))
            commas++;
    }

    int startHighlight = 0;
    while (functionDefinition[startHighlight] && !calltipParametersStart.contains(functionDefinition[startHighlight]))
        startHighlight++;
    if (calltipParametersStart.contains(functionDefinition[startHighlight]))
        startHighlight++;
    while (functionDefinition[startHighlight] && commas > 0) {
        if (calltipParametersSeparators.contains(functionDefinition[startHighlight]))
            commas--;
        // If it reached the end of the argument list it means that the user typed in more
        // arguments than the ones listed in the calltip
        if (calltipParametersEnd.contains(functionDefinition[startHighlight]))
            commas = 0;
        else
            startHighlight++;
    }
    if (calltipParametersSeparators.contains(functionDefinition[startHighlight]))
        startHighlight++;
    int endHighlight = startHighlight;
    while (functionDefinition[endHighlight] && !calltipParametersSeparators.contains(functionDefinition[endHighlight]) && !calltipParametersEnd.contains(functionDefinition[endHighlight]))
        endHighlight++;

    SendEditor(SCI_CALLTIPSETHLT, startHighlight, endHighlight);
}

void SciTEBase::EliminateDuplicateWords(WCHAR *words) {
    WCHAR *firstWord = words;
    WCHAR *firstSpace = wcschr(firstWord, L' ');
    WCHAR *secondWord;
    WCHAR *secondSpace;
    int firstLen, secondLen;

    while (firstSpace) {
        firstLen = firstSpace - firstWord;
        secondWord = firstWord + firstLen + 1;
        secondSpace = wcschr(secondWord, L' ');

        if (secondSpace)
            secondLen = secondSpace - secondWord;
        else
            secondLen = wcslen(secondWord);

        if (firstLen == secondLen &&
            !wcsncmp(firstWord, secondWord, firstLen)) {
                wcscpy(firstWord, secondWord);
                firstSpace = wcschr(firstWord, L' ');
        } else {
            firstWord = secondWord;
            firstSpace = secondSpace;
        }
    }
}

bool SciTEBase::StartAutoComplete() {
    SString line = GetLine();
    int current = GetCaretInLine();

    int startword = current;

    while ((startword > 0) &&
        (calltipWordCharacters.contains(line[startword - 1]) ||
        autoCompleteStartCharacters.contains(line[startword - 1]))) {
            startword--;
    }

    SString root = line.substr(startword, current - startword);
    if (apis) {
        WCHAR *words = GetNearestWords(root.c_str(), root.length(),
            calltipParametersStart.c_str(), autoCompleteIgnoreCase);
        if (words) {
            EliminateDuplicateWords(words);
            SendEditorString(SCI_AUTOCSHOW, root.length(), words);
            delete []words;
        }
    }
    return true;
}

bool SciTEBase::StartAutoCompleteWord(bool onlyOneWord) {
    SString line = GetLine();
    int current = GetCaretInLine();

    int startword = current;
    // Autocompletion of pure numbers is mostly an annoyance
    bool allNumber = true;
    while (startword > 0 && wordCharacters.contains(line[startword - 1])) {
        startword--;
        if (line[startword] < L'0' || line[startword] > L'9') {
            allNumber = false;
        }
    }
    if (startword == current || allNumber)
        return true;
    SString root = line.substr(startword, current - startword);
    int doclen = LengthDocument();
    Sci_TextToFind ft = {{0, 0}, 0, {0, 0}};
    ft.lpstrText = const_cast<WCHAR*>(root.c_str());
    ft.chrg.cpMin = 0;
    ft.chrg.cpMax = doclen;
    ft.chrgText.cpMin = 0;
    ft.chrgText.cpMax = 0;
    const int flags = SCFIND_WORDSTART | (autoCompleteIgnoreCase ? 0 : SCFIND_MATCHCASE);
    int posCurrentWord = SendEditor(SCI_GETCURRENTPOS) - root.length();
    unsigned int minWordLength = 0;
    unsigned int nwords = 0;

    // wordsNear contains a list of words separated by single spaces and with a space
    // at the start and end. This makes it easy to search for words.
    SString wordsNear;
    wordsNear.setsizegrowth(1000);
	wordsNear.append(L"\n");

    int posFind = SendEditorString(SCI_FINDTEXT, flags, reinterpret_cast<WCHAR *>(&ft));
    WindowAccessor acc(wEditor.GetID(), props);
    while (posFind >= 0 && posFind < doclen) {	// search all the document
        int wordEnd = posFind + root.length();
        if (posFind != posCurrentWord) {
            while (wordCharacters.contains(acc.SafeGetCharAt(wordEnd)))
                wordEnd++;
            size_t wordLength = wordEnd - posFind;
            if (wordLength > root.length()) {
                SString word = GetRange(wEditor, posFind, wordEnd);
				word.insert(0, L"\n");
				word.append(L"\n");
                if (!wordsNear.contains(word.c_str())) {	// add a new entry
                    wordsNear += word.c_str() + 1;
                    if (minWordLength < wordLength)
                        minWordLength = wordLength;

                    nwords++;
                    if (onlyOneWord && nwords > 1) {
                        return true;
                    }
                }
            }
        }
        ft.chrg.cpMin = wordEnd;
        posFind = SendEditorString(SCI_FINDTEXT, flags, reinterpret_cast<WCHAR *>(&ft));
    }
    size_t length = wordsNear.length();
    if ((length > 2) && (!onlyOneWord || (minWordLength > root.length()))) {
		// Protect spaces by temporrily transforming to \001
		wordsNear.substitute(L' ', L'\001');
		StringList wl(true);
		wl.Set(wordsNear.c_str());
		WCHAR *words = wl.GetNearestWords(L"", 0, autoCompleteIgnoreCase);
		SString acText(words);
		// Use \n as word separator
		acText.substitute(L' ', L'\n');
		// Return spaces from \001
		acText.substitute(L'\001', L' ');
		SendEditor(SCI_AUTOCSETSEPARATOR, L'\n');
		SendEditorString(SCI_AUTOCSHOW, root.length(), acText.c_str());
        delete []words;
    } else {
        SendEditor(SCI_AUTOCCANCEL);
    }
    return true;
}

bool SciTEBase::StartInsertAbbreviation() {
    if (!AbbrevDialog()) {
        return true;
    }

    SString data = propsAbbrev.Get(abbrevInsert);
    size_t dataLength = data.length();
    if (dataLength == 0) {
        return true; // returning if expanded abbreviation is empty
    }

    WCHAR *expbuf = new WCHAR[dataLength + 1];
    wcscpy(expbuf, data.c_str());
    UnSlash(expbuf);
    size_t expbuflen = wcslen(expbuf);
    int caret_pos = SendEditor(SCI_GETSELECTIONSTART);
    int sel_start = caret_pos;
    int sel_length = SendEditor(SCI_GETSELECTIONEND) - sel_start;
    bool at_start = true;
    bool double_pipe = false;
    size_t last_pipe = expbuflen;
    int currentLineNumber = SendEditor(SCI_LINEFROMPOSITION, caret_pos);
    int indent = 0;
    int indentSize = SendEditor(SCI_GETINDENT);
    int indentChars = (SendEditor(SCI_GETUSETABS) && SendEditor(SCI_GETTABWIDTH) ? SendEditor(SCI_GETTABWIDTH) : 1);
    int indentExtra = 0;
    bool isIndent = true;
    int eolMode = SendEditor(SCI_GETEOLMODE);
    if (props.GetInt(L"indent.automatic")) {
        indent = GetLineIndentation(currentLineNumber);
    }

    size_t i;
    // find last |, can't be strrchr(exbuf, '|') because of ||
    for (i = expbuflen; i--; ) {
        if (expbuf[i] == L'|' && (i == 0 || expbuf[i-1] != L'|')) {
            last_pipe = i;
            break;
        }
    }

    SendEditor(SCI_BEGINUNDOACTION);

    // add the abbreviation one character at a time
    for (i = 0; i < expbuflen; i++) {
        WCHAR c = expbuf[i];
        SString abbrevText(L"");
        if (isIndent && c == L'\t') {
            if (props.GetInt(L"indent.automatic")) {
                indentExtra++;
                SetLineIndentation(currentLineNumber, indent + indentSize * indentExtra);
                caret_pos += indentSize / indentChars;
            }
        } else {
            switch (c) {
            case '|':
                // user may want to insert '|' instead of caret
                if (i < (dataLength - 1) && expbuf[i + 1] == '|') {
                    // put '|' into the line
                    abbrevText += c;
                    i++;
                } else if (i != last_pipe) {
                    double_pipe = true;
                } else {
                    // indent on multiple lines
                    int j = currentLineNumber + 1; // first line indented as others
                    currentLineNumber = SendEditor(SCI_LINEFROMPOSITION, caret_pos + sel_length);
                    for (; j <= currentLineNumber; j++) {
                        SetLineIndentation(j, GetLineIndentation(j) + indentSize * indentExtra);
                        caret_pos += indentExtra * indentSize / indentChars;
                    }

                    at_start = false;
                    caret_pos += sel_length;
                }
                break;
            case '\r':
                // backward compatibility
                break;
            case '\n':
                if (eolMode == SC_EOL_CRLF || eolMode == SC_EOL_CR) {
                    abbrevText += '\r';
                }
                if (eolMode == SC_EOL_CRLF || eolMode == SC_EOL_LF) {
                    abbrevText += '\n';
                }
                break;
            default:
                abbrevText += c;
                break;
            }
            if (caret_pos > SendEditor(SCI_GETLENGTH)) {
                caret_pos = SendEditor(SCI_GETLENGTH);
            }
            SendEditorString(SCI_INSERTTEXT, caret_pos, abbrevText.c_str());
            if (!double_pipe && at_start) {
                sel_start += static_cast<int>(abbrevText.length());
            }
            caret_pos += static_cast<int>(abbrevText.length());
            if (c == '\n') {
                isIndent = true;
                indentExtra = 0;
                currentLineNumber++;
                SetLineIndentation(currentLineNumber, indent);
                caret_pos += indent / indentChars;
                if (!double_pipe && at_start) {
                    sel_start += indent / indentChars;
                }
            } else {
                isIndent = false;
            }
        }
    }

    // set the caret to the desired position
    if (double_pipe) {
        sel_length = 0;
    } else if (!at_start && sel_length == 0) {
        sel_start += static_cast<int>(expbuflen);
    }
    SendEditor(SCI_SETSEL, sel_start, sel_start + sel_length);

    SendEditor(SCI_ENDUNDOACTION);
    delete []expbuf;
    return true;
}

bool SciTEBase::StartExpandAbbreviation() {
    int currentPos = GetCaretInLine();
    int position = SendEditor(SCI_GETCURRENTPOS); // from the beginning
    WCHAR *linebuf = new WCHAR[currentPos + 2];
    GetLine(linebuf, currentPos + 2);	// Just get text to the left of the caret
    linebuf[currentPos] = '\0';
    int abbrevPos = (currentPos > 32 ? currentPos - 32 : 0);
    const WCHAR *abbrev = linebuf + abbrevPos;
    SString data;
    size_t dataLength = 0;
    int abbrevLength = currentPos - abbrevPos;
    // Try each potential abbreviation from the first letter on a line
    // and expanding to the right.
    // We arbitrarily limits the length of an abbreviation (seems a reasonable value..),
    // and of course stop on the caret.
    while (abbrevLength > 0) {
        data = propsAbbrev.Get(abbrev);
        dataLength = data.length();
        if (dataLength > 0) {
            break;	/* Found */
        }
        abbrev++;	// One more letter to the right
        abbrevLength--;
    }

    if (dataLength == 0) {
        delete []linebuf;
        WarnUser(warnNotFound);	// No need for a special warning
        return true; // returning if expanded abbreviation is empty
    }

    WCHAR *expbuf = new WCHAR[dataLength + 1];
    wcscpy(expbuf, data.c_str());
    UnSlash(expbuf);
    size_t expbuflen = wcslen(expbuf);
    int caret_pos = -1; // caret position
    int currentLineNumber = GetCurrentLineNumber();
    int indent = 0;
    int indentExtra = 0;
    bool isIndent = true;
    int eolMode = SendEditor(SCI_GETEOLMODE);
    if (props.GetInt(L"indent.automatic")) {
        indent = GetLineIndentation(currentLineNumber);
    }

    SendEditor(SCI_BEGINUNDOACTION);
    SendEditor(SCI_SETSEL, position - abbrevLength, position);

    // add the abbreviation one character at a time
    for (size_t i = 0; i < expbuflen; i++) {
        WCHAR c = expbuf[i];
        SString abbrevText(L"");
        if (isIndent && c == L'\t') {
            indentExtra++;
            SetLineIndentation(currentLineNumber, indent + SendEditor(SCI_GETINDENT) * indentExtra);
        } else {
            switch (c) {
            case L'|':
                // user may want to insert '|' instead of caret
                if (i < (dataLength - 1) && expbuf[i + 1] == L'|') {
                    // put '|' into the line
                    abbrevText += c;
                    i++;
                } else if (caret_pos == -1) {
                    if (i == 0) {
                        // when caret is set at the first place in abbreviation
                        caret_pos = SendEditor(SCI_GETCURRENTPOS) - abbrevLength;
                    } else {
                        caret_pos = SendEditor(SCI_GETCURRENTPOS);
                    }
                }
                break;
            case L'\n':
                if (eolMode == SC_EOL_CRLF || eolMode == SC_EOL_CR) {
                    abbrevText += L'\r';
                }
                if (eolMode == SC_EOL_CRLF || eolMode == SC_EOL_LF) {
                    abbrevText += L'\n';
                }
                break;
            default:
                abbrevText += c;
                break;
            }
            SendEditorString(SCI_REPLACESEL, 0, abbrevText.c_str());
            if (c == '\n') {
                isIndent = true;
                indentExtra = 0;
                currentLineNumber++;
                SetLineIndentation(currentLineNumber, indent);
            } else {
                isIndent = false;
            }
        }
    }

    // set the caret to the desired position
    if (caret_pos != -1) {
        SendEditor(SCI_GOTOPOS, caret_pos);
    }

    SendEditor(SCI_ENDUNDOACTION);
    delete []expbuf;
    delete []linebuf;
    return true;
}

bool SciTEBase::StartBlockComment() {
    SString fileNameForExtension = ExtensionFileName();
    SString lexerName = props.GetNewExpand(L"lexer.", fileNameForExtension.c_str());
    SString base(L"comment.block.");
    SString comment_at_line_start(L"comment.block.at.line.start.");
    base += lexerName;
    comment_at_line_start += lexerName;
    bool placeCommentsAtLineStart = props.GetInt(comment_at_line_start.c_str()) != 0;

    SString comment = props.Get(base.c_str());
    if (comment == L"") { // user friendly error message box
        SString error = LocaliseMessage(
            L"Block comment variable '^0' is not defined in SciTE *.properties!", base.c_str());
        WindowMessageBox(wSciTE, error, MB_OK | MB_ICONWARNING);
        return true;
    }
    SString long_comment = comment;
    long_comment.append(" ");
    int selectionStart = SendEditor(SCI_GETSELECTIONSTART);
    int selectionEnd = SendEditor(SCI_GETSELECTIONEND);
    int caretPosition = SendEditor(SCI_GETCURRENTPOS);
    // checking if caret is located in _beginning_ of selected block
    bool move_caret = caretPosition < selectionEnd;
    int selStartLine = SendEditor(SCI_LINEFROMPOSITION, selectionStart);
    int selEndLine = SendEditor(SCI_LINEFROMPOSITION, selectionEnd);
    int lines = selEndLine - selStartLine;
    int firstSelLineStart = SendEditor(SCI_POSITIONFROMLINE, selStartLine);
    // "caret return" is part of the last selected line
    if ((lines > 0) &&
        (selectionEnd == SendEditor(SCI_POSITIONFROMLINE, selEndLine)))
        selEndLine--;
    SendEditor(SCI_BEGINUNDOACTION);
    for (int i = selStartLine; i <= selEndLine; i++) {
        int lineStart = SendEditor(SCI_POSITIONFROMLINE, i);
        int lineIndent = lineStart;
        int lineEnd = SendEditor(SCI_GETLINEENDPOSITION, i);
        if (!placeCommentsAtLineStart) {
            lineIndent = GetLineIndentPosition(i);
        }
        SString linebuf = GetRange(wEditor, lineIndent, lineEnd);
        // empty lines are not commented
        if (linebuf.length() < 1)
            continue;
        if (linebuf.startswith(comment.c_str())) {
            int commentLength = comment.length();
            if (linebuf.startswith(long_comment.c_str())) {
                // Removing comment with space after it.
                commentLength = long_comment.length();
            }
            SendEditor(SCI_SETSEL, lineIndent, lineIndent + commentLength);
            SendEditorString(SCI_REPLACESEL, 0, L"");
            if (i == selStartLine) // is this the first selected line?
                selectionStart -= commentLength;
            selectionEnd -= commentLength; // every iteration
            continue;
        }
        if (i == selStartLine) // is this the first selected line?
            selectionStart += long_comment.length();
        selectionEnd += long_comment.length(); // every iteration
        SendEditorString(SCI_INSERTTEXT, lineIndent, long_comment.c_str());
    }
    // after uncommenting selection may promote itself to the lines
    // before the first initially selected line;
    // another problem - if only comment symbol was selected;
    if (selectionStart < firstSelLineStart) {
        if (selectionStart >= selectionEnd - (static_cast<int>(long_comment.length()) - 1))
            selectionEnd = firstSelLineStart;
        selectionStart = firstSelLineStart;
    }
    if (move_caret) {
        // moving caret to the beginning of selected block
        SendEditor(SCI_GOTOPOS, selectionEnd);
        SendEditor(SCI_SETCURRENTPOS, selectionStart);
    } else {
        SendEditor(SCI_SETSEL, selectionStart, selectionEnd);
    }
    SendEditor(SCI_ENDUNDOACTION);
    return true;
}

static const WCHAR *LineEndString(int eolMode) {
    switch (eolMode) {
        case SC_EOL_CRLF:
            return L"\r\n";
        case SC_EOL_CR:
            return L"\r";
        case SC_EOL_LF:
        default:
            return L"\n";
    }
}

bool SciTEBase::StartBoxComment() {
    // Get start/middle/end comment strings from options file(s)
    SString fileNameForExtension = ExtensionFileName();
    SString lexerName = props.GetNewExpand(L"lexer.", fileNameForExtension.c_str());
    SString start_base(L"comment.box.start.");
    SString middle_base(L"comment.box.middle.");
    SString end_base(L"comment.box.end.");
    SString white_space(L" ");
    SString eol(LineEndString(SendEditor(SCI_GETEOLMODE)));
    start_base += lexerName;
    middle_base += lexerName;
    end_base += lexerName;
    SString start_comment = props.Get(start_base.c_str());
    SString middle_comment = props.Get(middle_base.c_str());
    SString end_comment = props.Get(end_base.c_str());
    if (start_comment == L"" || middle_comment == L"" || end_comment == L"") {
        SString error = LocaliseMessage(
            L"Box comment variables '^0', '^1' and '^2' are not defined in SciTE *.properties!",
            start_base.c_str(), middle_base.c_str(), end_base.c_str());
        WindowMessageBox(wSciTE, error, MB_OK | MB_ICONWARNING);
        return true;
    }

    // Note selection and cursor location so that we can reselect text and reposition cursor after we insert comment strings
    size_t selectionStart = SendEditor(SCI_GETSELECTIONSTART);
    size_t selectionEnd = SendEditor(SCI_GETSELECTIONEND);
    size_t caretPosition = SendEditor(SCI_GETCURRENTPOS);
    bool move_caret = caretPosition < selectionEnd;
    size_t selStartLine = SendEditor(SCI_LINEFROMPOSITION, selectionStart);
    size_t selEndLine = SendEditor(SCI_LINEFROMPOSITION, selectionEnd);
    size_t lines = selEndLine - selStartLine + 1;

    // If selection ends at start of last selected line, fake it so that selection goes to end of second-last selected line
    if (lines > 1 && selectionEnd == static_cast<size_t>(SendEditor(SCI_POSITIONFROMLINE, selEndLine))) {
        selEndLine--;
        lines--;
        selectionEnd = SendEditor(SCI_GETLINEENDPOSITION, selEndLine);
    }

    // Pad comment strings with appropriate whitespace, then figure out their lengths (end_comment is a bit special-- see below)
    start_comment += white_space;
    middle_comment += white_space;
    size_t start_comment_length = start_comment.length();
    size_t middle_comment_length = middle_comment.length();
    size_t end_comment_length = end_comment.length();
    size_t whitespace_length = white_space.length();

    // Calculate the length of the longest comment string to be inserted, and allocate a null-terminated char buffer of equal size
    size_t maxCommentLength = start_comment_length;
    if (middle_comment_length > maxCommentLength)
        maxCommentLength = middle_comment_length;
    if (end_comment_length + whitespace_length > maxCommentLength)
        maxCommentLength = end_comment_length + whitespace_length;
    WCHAR *tempString = new WCHAR[maxCommentLength + 1];

    SendEditor(SCI_BEGINUNDOACTION);

    // Insert start_comment if needed
    int lineStart = SendEditor(SCI_POSITIONFROMLINE, selStartLine);
    GetRange(wEditor, lineStart, lineStart + start_comment_length, tempString);
    tempString[start_comment_length] = L'\0';
    if (start_comment != tempString) {
        SendEditorString(SCI_INSERTTEXT, lineStart, start_comment.c_str());
        selectionStart += start_comment_length;
        selectionEnd += start_comment_length;
    }

    if (lines <= 1) {
        // Only a single line was selected, so just append whitespace + end-comment at end of line if needed
        int lineEnd = SendEditor(SCI_GETLINEENDPOSITION, selEndLine);
        GetRange(wEditor, lineEnd - end_comment_length, lineEnd, tempString);
        tempString[end_comment_length] = L'\0';
        if (end_comment != tempString) {
            end_comment.insert(0, white_space.c_str());
            SendEditorString(SCI_INSERTTEXT, lineEnd, end_comment.c_str());
        }
    } else {
        // More than one line selected, so insert middle_comments where needed
        for (size_t i = selStartLine + 1; i < selEndLine; i++) {
            lineStart = SendEditor(SCI_POSITIONFROMLINE, i);
            GetRange(wEditor, lineStart, lineStart + middle_comment_length, tempString);
            tempString[middle_comment_length] = L'\0';
            if (middle_comment != tempString) {
                SendEditorString(SCI_INSERTTEXT, lineStart, middle_comment.c_str());
                selectionEnd += middle_comment_length;
            }
        }

        // If last selected line is not middle-comment or end-comment, we need to insert
        // a middle-comment at the start of last selected line and possibly still insert
        // and end-comment tag after the last line (extra logic is necessary to
        // deal with the case that user selected the end-comment tag)
        lineStart = SendEditor(SCI_POSITIONFROMLINE, selEndLine);
        GetRange(wEditor, lineStart, lineStart + end_comment_length, tempString);
        tempString[end_comment_length] = L'\0';
        if (end_comment != tempString) {
            GetRange(wEditor, lineStart, lineStart + middle_comment_length, tempString);
            tempString[middle_comment_length] = L'\0';
            if (middle_comment != tempString) {
                SendEditorString(SCI_INSERTTEXT, lineStart, middle_comment.c_str());
                selectionEnd += middle_comment_length;
            }

            // And since we didn't find the end-comment string yet, we need to check the *next* line
            //  to see if it's necessary to insert an end-comment string and a linefeed there....
            lineStart = SendEditor(SCI_POSITIONFROMLINE, selEndLine + 1);
            GetRange(wEditor, lineStart, lineStart + (int) end_comment_length, tempString);
            tempString[end_comment_length] = '\0';
            if (end_comment != tempString) {
                end_comment += eol;
                SendEditorString(SCI_INSERTTEXT, lineStart, end_comment.c_str());
            }
        }
    }

    if (move_caret) {
        // moving caret to the beginning of selected block
        SendEditor(SCI_GOTOPOS, selectionEnd);
        SendEditor(SCI_SETCURRENTPOS, selectionStart);
    } else {
        SendEditor(SCI_SETSEL, selectionStart, selectionEnd);
    }

    SendEditor(SCI_ENDUNDOACTION);

    delete[] tempString;

    return true;
}

bool SciTEBase::StartStreamComment() {
    SString fileNameForExtension = ExtensionFileName();
    SString lexerName = props.GetNewExpand(L"lexer.", fileNameForExtension.c_str());
    SString start_base(L"comment.stream.start.");
    SString end_base(L"comment.stream.end.");
    SString white_space(L" ");
    start_base += lexerName;
    end_base += lexerName;
    SString start_comment = props.Get(start_base.c_str());
    SString end_comment = props.Get(end_base.c_str());
    if (start_comment == L"" || end_comment == L"") {
        SString error = LocaliseMessage(
            L"Stream comment variables '^0' and '^1' are not defined in SciTE *.properties!",
            start_base.c_str(), end_base.c_str());
        WindowMessageBox(wSciTE, error, MB_OK | MB_ICONWARNING);
        return true;
    }
    start_comment += white_space;
    white_space += end_comment;
    end_comment = white_space;
    size_t start_comment_length = start_comment.length();
    size_t selectionStart = SendEditor(SCI_GETSELECTIONSTART);
    size_t selectionEnd = SendEditor(SCI_GETSELECTIONEND);
    size_t caretPosition = SendEditor(SCI_GETCURRENTPOS);
    // checking if caret is located in _beginning_ of selected block
    bool move_caret = caretPosition < selectionEnd;
    // if there is no selection?
    if (selectionEnd - selectionStart <= 0) {
        int selLine = SendEditor(SCI_LINEFROMPOSITION, selectionStart);
        int lineIndent = GetLineIndentPosition(selLine);
        int lineEnd = SendEditor(SCI_GETLINEENDPOSITION, selLine);
        if (RangeIsAllWhitespace(lineIndent, lineEnd))
            return true; // we are not dealing with empty lines
        WCHAR linebuf[1000];
        GetLine(linebuf, sizeof(linebuf) / sizeof(WCHAR));
        int current = GetCaretInLine();
        // checking if we are not inside a word
        if (!wordCharacters.contains(linebuf[current]))
            return true; // caret is located _between_ words
        int startword = current;
        int endword = current;
        int start_counter = 0;
        int end_counter = 0;
        while (startword > 0 && wordCharacters.contains(linebuf[startword - 1])) {
            start_counter++;
            startword--;
        }
        // checking _beginning_ of the word
        if (startword == current)
            return true; // caret is located _before_ a word
        while (linebuf[endword + 1] != L'\0' && wordCharacters.contains(linebuf[endword + 1])) {
            end_counter++;
            endword++;
        }
        selectionStart -= start_counter;
        selectionEnd += (end_counter + 1);
    }
    SendEditor(SCI_BEGINUNDOACTION);
    SendEditorString(SCI_INSERTTEXT, selectionStart, start_comment.c_str());
    selectionEnd += start_comment_length;
    selectionStart += start_comment_length;
    SendEditorString(SCI_INSERTTEXT, selectionEnd, end_comment.c_str());
    if (move_caret) {
        // moving caret to the beginning of selected block
        SendEditor(SCI_GOTOPOS, selectionEnd);
        SendEditor(SCI_SETCURRENTPOS, selectionStart);
    } else {
        SendEditor(SCI_SETSEL, selectionStart, selectionEnd);
    }
    SendEditor(SCI_ENDUNDOACTION);
    return true;
}

/**
* Return the length of the given line, not counting the EOL.
*/
int SciTEBase::GetLineLength(int line) {
    return SendEditor(SCI_GETLINEENDPOSITION, line) - SendEditor(SCI_POSITIONFROMLINE, line);
}

int SciTEBase::GetCurrentLineNumber() {
    return SendEditor(SCI_LINEFROMPOSITION,
        SendEditor(SCI_GETCURRENTPOS));
}

int SciTEBase::GetCurrentScrollPosition() {
    int lineDisplayTop = SendEditor(SCI_GETFIRSTVISIBLELINE);
    return SendEditor(SCI_DOCLINEFROMVISIBLE, lineDisplayTop);
}

/**
* Set up properties for ReadOnly, EOLMode, BufferLength, NbOfLines, SelLength, SelHeight.
*/
void SciTEBase::SetTextProperties(
                                  PropSetFile &ps) {			///< Property set to update.

                                      const int TEMP_LEN = 100;
                                      WCHAR temp[TEMP_LEN];

                                      SString ro = localiser.Text(L"READ");
                                      ps.Set(L"ReadOnly", isReadOnly ? ro.c_str() : L"");

                                      int eolMode = SendEditor(SCI_GETEOLMODE);
                                      ps.Set(L"EOLMode", eolMode == SC_EOL_CRLF ? L"CR+LF" : (eolMode == SC_EOL_LF ? L"LF" : L"CR"));

                                      wsprintf(temp, L"%d", LengthDocument());
                                      ps.Set(L"BufferLength", temp);

                                      ps.SetInteger(L"NbOfLines", SendEditor(SCI_GETLINECOUNT));

                                      Sci_CharacterRange crange = GetSelection();
                                      int selFirstLine = SendEditor(SCI_LINEFROMPOSITION, crange.cpMin);
                                      int selLastLine = SendEditor(SCI_LINEFROMPOSITION, crange.cpMax);
                                      if (SendEditor(SCI_GETSELECTIONMODE) == SC_SEL_RECTANGLE) {
                                          long charCount = 0;
                                          for (int line = selFirstLine; line <= selLastLine; line++) {
                                              int startPos = SendEditor(SCI_GETLINESELSTARTPOSITION, line);
                                              int endPos = SendEditor(SCI_GETLINESELENDPOSITION, line);
                                              charCount += endPos - startPos;
                                          }
                                          wsprintf(temp, L"%ld", charCount);
                                      } else {
                                          wsprintf(temp, L"%ld", crange.cpMax - crange.cpMin);
                                      }
                                      ps.Set(L"SelLength", temp);
                                      int caretPos = SendEditor(SCI_GETCURRENTPOS);
                                      int selAnchor = SendEditor(SCI_GETANCHOR);
                                      if (0 == (crange.cpMax - crange.cpMin)) {
                                          wsprintf(temp, L"%d", 0);
                                      } else if (selLastLine == selFirstLine) {
                                          wsprintf(temp, L"%d", 1);
                                      } else if ((SendEditor(SCI_GETCOLUMN, caretPos) == 0 && (selAnchor <= caretPos)) ||
                                          ((SendEditor( SCI_GETCOLUMN, selAnchor) == 0) && (selAnchor > caretPos ))) {
                                              wsprintf(temp, L"%d", selLastLine - selFirstLine);
                                      } else {
                                          wsprintf(temp, L"%d", selLastLine - selFirstLine + 1);
                                      }
                                      ps.Set(L"SelHeight", temp);
}

void SciTEBase::UpdateStatusBar(bool bUpdateSlowData) {
    if (sbVisible) {
        if (bUpdateSlowData) {
            SetFileProperties(propsStatus);
        }
        SetTextProperties(propsStatus);
        int caretPos = SendEditor(SCI_GETCURRENTPOS);
        propsStatus.SetInteger(L"LineNumber",
            SendEditor(SCI_LINEFROMPOSITION, caretPos) + 1);
        propsStatus.SetInteger(L"ColumnNumber",
            SendEditor(SCI_GETCOLUMN, caretPos) + 1);
        propsStatus.Set(L"OverType", SendEditor(SCI_GETOVERTYPE) ? L"OVR" : L"INS");

        WCHAR sbKey[32];
        wsprintf(sbKey, L"statusbar.text.%d", sbNum);
        SString msg = propsStatus.GetExpanded(sbKey);
        if (msg.size() && sbValue != msg) {	// To avoid flickering, update only if needed
            SetStatusBarText(msg.c_str());
            sbValue = msg;
        }
    } else {
        sbValue = L"";
    }
}

void SciTEBase::SetLineIndentation(int line, int indent) {
    if (indent < 0)
        return;
    Sci_CharacterRange crange = GetSelection();
    int posBefore = GetLineIndentPosition(line);
    SendEditor(SCI_SETLINEINDENTATION, line, indent);
    int posAfter = GetLineIndentPosition(line);
    int posDifference = posAfter - posBefore;
    if (posAfter > posBefore) {
        // Move selection on
        if (crange.cpMin >= posBefore) {
            crange.cpMin += posDifference;
        }
        if (crange.cpMax >= posBefore) {
            crange.cpMax += posDifference;
        }
    } else if (posAfter < posBefore) {
        // Move selection back
        if (crange.cpMin >= posAfter) {
            if (crange.cpMin >= posBefore)
                crange.cpMin += posDifference;
            else
                crange.cpMin = posAfter;
        }
        if (crange.cpMax >= posAfter) {
            if (crange.cpMax >= posBefore)
                crange.cpMax += posDifference;
            else
                crange.cpMax = posAfter;
        }
    }
    SetSelection(crange.cpMin, crange.cpMax);
}

int SciTEBase::GetLineIndentation(int line) {
    return SendEditor(SCI_GETLINEINDENTATION, line);
}

int SciTEBase::GetLineIndentPosition(int line) {
    return SendEditor(SCI_GETLINEINDENTPOSITION, line);
}

static SString CreateIndentation(int indent, int tabSize, bool insertSpaces) {
    SString indentation;
    if (!insertSpaces) {
        while (indent >= tabSize) {
            indentation.append("\t", 1);
            indent -= tabSize;
        }
    }
    while (indent > 0) {
        indentation.append(" ", 1);
        indent--;
    }
    return indentation;
}

void SciTEBase::ConvertIndentation(int tabSize, int useTabs) {
    SendEditor(SCI_BEGINUNDOACTION);
    int maxLine = SendEditor(SCI_GETLINECOUNT);
    for (int line = 0; line < maxLine; line++) {
        int lineStart = SendEditor(SCI_POSITIONFROMLINE, line);
        int indent = GetLineIndentation(line);
        int indentPos = GetLineIndentPosition(line);
        const int maxIndentation = 1000;
        if (indent < maxIndentation) {
            SString indentationNow = GetRange(wEditor, lineStart, indentPos);
            SString indentationWanted = CreateIndentation(indent, tabSize, !useTabs);
            if (indentationNow != indentationWanted) {
                SendEditor(SCI_SETTARGETSTART, lineStart);
                SendEditor(SCI_SETTARGETEND, indentPos);
                SendEditorString(SCI_REPLACETARGET, indentationWanted.length(),
                    indentationWanted.c_str());
            }
        }
    }
    SendEditor(SCI_ENDUNDOACTION);
}

bool SciTEBase::RangeIsAllWhitespace(int start, int end) {
    WindowAccessor acc(wEditor.GetID(), props);
    for (int i = start;i < end;i++) {
        if ((acc[i] != ' ') && (acc[i] != '\t'))
            return false;
    }
    return true;
}

unsigned int SciTEBase::GetLinePartsInStyle(int line, int style1, int style2, SString sv[], int len) {
    for (int i = 0; i < len; i++)
        sv[i] = L"";
    WindowAccessor acc(wEditor.GetID(), props);
    SString s;
    int part = 0;
    int thisLineStart = SendEditor(SCI_POSITIONFROMLINE, line);
    int nextLineStart = SendEditor(SCI_POSITIONFROMLINE, line + 1);
    for (int pos = thisLineStart; pos < nextLineStart; pos++) {
        if ((acc.StyleAt(pos) == style1) || (acc.StyleAt(pos) == style2)) {
            WCHAR c[2];
            c[0] = acc[pos];
            c[1] = L'\0';
            s += c;
        } else if (s.length() > 0) {
            if (part < len) {
                sv[part++] = s;
            }
            s = L"";
        }
    }
    if ((s.length() > 0) && (part < len)) {
        sv[part++] = s;
    }
    return part;
}

inline bool IsAlphabetic(unsigned int ch) {
    return ((ch >= 'A') && (ch <= 'Z')) || ((ch >= 'a') && (ch <= 'z'));
}

static bool includes(const StyleAndWords &symbols, const SString value) {
    if (symbols.words.length() == 0) {
        return false;
    } else if (IsAlphabetic(symbols.words[0])) {
        // Set of symbols separated by spaces
        size_t lenVal = value.length();
        const WCHAR *symbol = symbols.words.c_str();
        while (symbol) {
            const WCHAR *symbolEnd = wcschr(symbol, L' ');
            size_t lenSymbol = wcslen(symbol);
            if (symbolEnd)
                lenSymbol = symbolEnd - symbol;
            if (lenSymbol == lenVal) {
                if (wcsncmp(symbol, value.c_str(), lenSymbol) == 0) {
                    return true;
                }
            }
            symbol = symbolEnd;
            if (symbol)
                symbol++;
        }
    } else {
        // Set of individual characters. Only one character allowed for now
        WCHAR ch = symbols.words[0];
        return wcschr(value.c_str(), ch) != 0;
    }
    return false;
}

IndentationStatus SciTEBase::GetIndentState(int line) {
    // C like language indentation defined by braces and keywords
    IndentationStatus indentState = isNone;
    SString controlWords[20];
    unsigned int parts = GetLinePartsInStyle(line, statementIndent.styleNumber,
        -1, controlWords, ELEMENTS(controlWords));
    unsigned int i;
    for (i = 0; i < parts; i++) {
        if (includes(statementIndent, controlWords[i]))
            indentState = isKeyWordStart;
    }
    parts = GetLinePartsInStyle(line, statementEnd.styleNumber,
        -1, controlWords, ELEMENTS(controlWords));
    for (i = 0; i < parts; i++) {
        if (includes(statementEnd, controlWords[i]))
            indentState = isNone;
    }
    // Braces override keywords
    SString controlStrings[20];
    parts = GetLinePartsInStyle(line, blockEnd.styleNumber,
        -1, controlStrings, ELEMENTS(controlStrings));
    for (unsigned int j = 0; j < parts; j++) {
        if (includes(blockEnd, controlStrings[j]))
            indentState = isBlockEnd;
        if (includes(blockStart, controlStrings[j]))
            indentState = isBlockStart;
    }
    return indentState;
}

int SciTEBase::IndentOfBlock(int line) {
    if (line < 0)
        return 0;
    int indentSize = SendEditor(SCI_GETINDENT);
    int indentBlock = GetLineIndentation(line);
    int backLine = line;
    IndentationStatus indentState = isNone;
    if (statementIndent.IsEmpty() && blockStart.IsEmpty() && blockEnd.IsEmpty())
        indentState = isBlockStart;	// Don't bother searching backwards

    int lineLimit = line - statementLookback;
    if (lineLimit < 0)
        lineLimit = 0;
    while ((backLine >= lineLimit) && (indentState == 0)) {
        indentState = GetIndentState(backLine);
        if (indentState != 0) {
            indentBlock = GetLineIndentation(backLine);
            if (indentState == isBlockStart) {
                if (!indentOpening)
                    indentBlock += indentSize;
            }
            if (indentState == isBlockEnd) {
                if (indentClosing)
                    indentBlock -= indentSize;
                if (indentBlock < 0)
                    indentBlock = 0;
            }
            if ((indentState == isKeyWordStart) && (backLine == line))
                indentBlock += indentSize;
        }
        backLine--;
    }
    return indentBlock;
}

void SciTEBase::MaintainIndentation(WCHAR ch) {
    int eolMode = SendEditor(SCI_GETEOLMODE);
    int curLine = GetCurrentLineNumber();
    int lastLine = curLine - 1;

    if (((eolMode == SC_EOL_CRLF || eolMode == SC_EOL_LF) && ch == L'\n') ||
        (eolMode == SC_EOL_CR && ch == L'\r')) {
            if (props.GetInt(L"indent.automatic")) {
                while (lastLine >= 0 && GetLineLength(lastLine) == 0)
                    lastLine--;
            }
		int indentAmount = 0;
            if (lastLine >= 0) {
                indentAmount = GetLineIndentation(lastLine);
            }
            if (indentAmount > 0) {
                SetLineIndentation(curLine, indentAmount);
            }
    }
}

void SciTEBase::AutomaticIndentation(WCHAR ch) {
    Sci_CharacterRange crange = GetSelection();
    int selStart = crange.cpMin;
    int curLine = GetCurrentLineNumber();
    int thisLineStart = SendEditor(SCI_POSITIONFROMLINE, curLine);
    int indentSize = SendEditor(SCI_GETINDENT);
    int indentBlock = IndentOfBlock(curLine - 1);

    if (blockEnd.IsSingleChar() && ch == blockEnd.words[0]) {	// Dedent maybe
        if (!indentClosing) {
            if (RangeIsAllWhitespace(thisLineStart, selStart - 1)) {
                SetLineIndentation(curLine, indentBlock - indentSize);
            }
        }
    } else if (!blockEnd.IsSingleChar() && (ch == ' ')) {	// Dedent maybe
        if (!indentClosing && (GetIndentState(curLine) == isBlockEnd)) {}
    } else if (blockStart.IsSingleChar() && (ch == blockStart.words[0])) {
        // Dedent maybe if first on line and previous line was starting keyword
        if (!indentOpening && (GetIndentState(curLine - 1) == isKeyWordStart)) {
            if (RangeIsAllWhitespace(thisLineStart, selStart - 1)) {
                SetLineIndentation(curLine, indentBlock - indentSize);
            }
        }
    } else if ((ch == L'\r' || ch == L'\n') && (selStart == thisLineStart)) {
        if (!indentClosing && !blockEnd.IsSingleChar()) {	// Dedent previous line maybe
            SString controlWords[1];
            if (GetLinePartsInStyle(curLine - 1, blockEnd.styleNumber,
                -1, controlWords, ELEMENTS(controlWords))) {
                    if (includes(blockEnd, controlWords[0])) {
                        // Check if first keyword on line is an ender
                        SetLineIndentation(curLine - 1, IndentOfBlock(curLine - 2) - indentSize);
                        // Recalculate as may have changed previous line
                        indentBlock = IndentOfBlock(curLine - 1);
                    }
            }
        }
        SetLineIndentation(curLine, indentBlock);
    }
}

/**
* Upon a character being added, SciTE may decide to perform some action
* such as displaying a completion list or auto-indentation.
*/
void SciTEBase::CharAdded(WCHAR ch) {
    if (recording)
        return;
    Sci_CharacterRange crange = GetSelection();
    int selStart = crange.cpMin;
    int selEnd = crange.cpMax;
    if ((selEnd == selStart) && (selStart > 0)) {
        if (SendEditor(SCI_CALLTIPACTIVE)) {
            if (calltipParametersEnd.contains(ch)) {
                braceCount--;
                if (braceCount < 1)
                    SendEditor(SCI_CALLTIPCANCEL);
                else
                    StartCallTip();
            } else if (calltipParametersStart.contains(ch)) {
                braceCount++;
                StartCallTip();
            } else {
                ContinueCallTip();
            }
        } else if (SendEditor(SCI_AUTOCACTIVE)) {
            if (calltipParametersStart.contains(ch)) {
                braceCount++;
                StartCallTip();
            } else if (calltipParametersEnd.contains(ch)) {
                braceCount--;
            } else if (!wordCharacters.contains(ch)) {
                SendEditor(SCI_AUTOCCANCEL);
                if (autoCompleteStartCharacters.contains(ch)) {
                    StartAutoComplete();
                }
            } else if (autoCCausedByOnlyOne) {
                StartAutoCompleteWord(true);
            }
        } else if (HandleXml(ch)) {
            // Handled in the routine
        } else {
            if (calltipParametersStart.contains(ch)) {
                braceCount = 1;
                StartCallTip();
            } else {
                autoCCausedByOnlyOne = false;
                if (indentMaintain)
                    MaintainIndentation(ch);
                else if (props.GetInt(L"indent.automatic"))
                    AutomaticIndentation(ch);
                if (autoCompleteStartCharacters.contains(ch)) {
                    StartAutoComplete();
                } else if (props.GetInt(L"autocompleteword.automatic") && wordCharacters.contains(ch)) {
                    StartAutoCompleteWord(true);
                    autoCCausedByOnlyOne = SendEditor(SCI_AUTOCACTIVE);
                }
            }
        }
    }
}

/**
* Upon a character being added to the output, SciTE may decide to perform some action
* such as displaying a completion list or running a shell command.
*/
void SciTEBase::CharAddedOutput(int ch) {
    if (ch == '\n') {
        NewLineInOutput();
    } else if (ch == '(') {
        // Potential autocompletion of symbols when $( typed
        int selStart = SendOutput(SCI_GETSELECTIONSTART);
        if ((selStart > 1) && (SendOutput(SCI_GETCHARAT, selStart - 2, 0) == L'$')) {
            SString symbols;
            const WCHAR *key = NULL;
            const WCHAR *val = NULL;
            bool b = props.GetFirst(key, val);
            while (b) {
                symbols.append(key);
                symbols.append(L") ");
                b = props.GetNext(key, val);
            }
            StringList symList;
            symList.Set(symbols.c_str());
            WCHAR *words = symList.GetNearestWords(L"", 0, true);
            if (words) {
                SendOutputString(SCI_AUTOCSHOW, 0, words);
                delete []words;
            }
        }
    }
}

/**
* This routine will auto complete XML or HTML tags that are still open by closing them
* @parm ch The characer we are dealing with, currently only works with the '>' character
* @return True if handled, false otherwise
*/
bool SciTEBase::HandleXml(WCHAR ch) {
    // We're looking for this char
    // Quit quickly if not found
    if (ch != L'>') {
        return false;
    }

    // This may make sense only in certain languages
    if (lexLanguage != SCLEX_HTML && lexLanguage != SCLEX_XML) {
        return false;
    }

    // If the user has turned us off, quit now.
    // Default is off
    SString value = props.GetExpanded(L"xml.auto.close.tags");
    if ((value.length() == 0) || (value == L"0")) {
        return false;
    }

    // Grab the last 512 characters or so
    int nCaret = SendEditor(SCI_GETCURRENTPOS);
    WCHAR sel[512];
    int nMin = nCaret - (sizeof(sel) / sizeof(WCHAR) - 1);
    if (nMin < 0) {
        nMin = 0;
    }

    if (nCaret - nMin < 3) {
        return false; // Smallest tag is 3 characters ex. <p>
    }
    GetRange(wEditor, nMin, nCaret, sel);
    sel[sizeof(sel) / sizeof(WCHAR) - 1] = L'\0';

    if (sel[nCaret - nMin - 2] == L'/') {
        // User typed something like "<br/>"
        return false;
    }

    SString strFound = FindOpenXmlTag(sel, nCaret - nMin);

    if (strFound.length() > 0) {
        SendEditor(SCI_BEGINUNDOACTION);
        SString toInsert = L"</";
        toInsert += strFound;
        toInsert += L">";
        SendEditorString(SCI_REPLACESEL, 0, toInsert.c_str());
        SetSelection(nCaret, nCaret);
        SendEditor(SCI_ENDUNDOACTION);
        return true;
    }

    return false;
}

/** Search backward through nSize bytes looking for a '<', then return the tag if any
* @return The tag name
*/
SString SciTEBase::FindOpenXmlTag(const WCHAR sel[], int nSize) {
    SString strRet = L"";

    if (nSize < 3) {
        // Smallest tag is "<p>" which is 3 characters
        return strRet;
    }
    const WCHAR* pBegin = &sel[0];
    const WCHAR* pCur = &sel[nSize - 1];

    pCur--; // Skip past the >
    while (pCur > pBegin) {
        if (*pCur == L'<') {
            break;
        } else if (*pCur == L'>') {
            break;
        }
        --pCur;
    }

    if (*pCur == L'<') {
        pCur++;
        while (wcschr(L":_-.", *pCur) || isalnum(*pCur)) {
            strRet += *pCur;
            pCur++;
        }
    }

    // Return the tag name or ""
    return strRet;
}

void SciTEBase::GoMatchingBrace(bool select) {
    int braceAtCaret = -1;
    int braceOpposite = -1;
    bool isInside = FindMatchingBracePosition(true, braceAtCaret, braceOpposite, true);
    // Convert the character positions into caret positions based on whether
    // the caret position was inside or outside the braces.
    if (isInside) {
        if (braceOpposite > braceAtCaret) {
            braceAtCaret++;
        } else {
            braceOpposite++;
        }
    } else {    // Outside
        if (braceOpposite > braceAtCaret) {
            braceOpposite++;
        } else {
            braceAtCaret++;
        }
    }
    if (braceOpposite >= 0) {
        EnsureRangeVisible(braceOpposite, braceOpposite);
        if (select) {
            SetSelection(braceAtCaret, braceOpposite);
        } else {
            SetSelection(braceOpposite, braceOpposite);
        }
    }
}

// Text	ConditionalUp	Ctrl+J	Finds the previous matching preprocessor condition
// Text	ConditionalDown	Ctrl+K	Finds the next matching preprocessor condition
void SciTEBase::GoMatchingPreprocCond(int direction, bool select) {
    int mppcAtCaret = SendEditor(SCI_GETCURRENTPOS);
    int mppcMatch = -1;
    int forward = (direction == IDM_NEXTMATCHPPC);
    bool isInside = FindMatchingPreprocCondPosition(forward, mppcAtCaret, mppcMatch);

    if (isInside && mppcMatch >= 0) {
        EnsureRangeVisible(mppcMatch, mppcMatch);
        if (select) {
            // Selection changes the rules a bit...
            int selStart = SendEditor(SCI_GETSELECTIONSTART);
            int selEnd = SendEditor(SCI_GETSELECTIONEND);
            // pivot isn't the caret position but the opposite (if there is a selection)
            int pivot = (mppcAtCaret == selStart ? selEnd : selStart);
            if (forward) {
                // Caret goes one line beyond the target, to allow selecting the whole line
                int lineNb = SendEditor(SCI_LINEFROMPOSITION, mppcMatch);
                mppcMatch = SendEditor(SCI_POSITIONFROMLINE, lineNb + 1);
            }
            SetSelection(pivot, mppcMatch);
        } else {
            SetSelection(mppcMatch, mppcMatch);
        }
    } else {
        WarnUser(warnNotFound);
    }
}

void SciTEBase::AddCommand(const SString &cmd, const SString &dir, JobSubsystem jobType, const SString &input, int flags) {
    if (jobQueue.commandCurrent >= jobQueue.commandMax)
        return;
    if (jobQueue.commandCurrent == 0)
        jobQueue.jobUsesOutputPane = false;
    if (cmd.length()) {
        jobQueue.jobQueue[jobQueue.commandCurrent].command = cmd;
        jobQueue.jobQueue[jobQueue.commandCurrent].directory.Set(dir.c_str());
        jobQueue.jobQueue[jobQueue.commandCurrent].jobType = jobType;
        jobQueue.jobQueue[jobQueue.commandCurrent].input = input;
        jobQueue.jobQueue[jobQueue.commandCurrent].flags = flags;
        jobQueue.commandCurrent++;
        if (jobType == jobCLI)
            jobQueue.jobUsesOutputPane = true;
        // For jobExtension, the Trace() method shows output pane on demand.
    }
}

int ControlIDOfCommand(unsigned long wParam) {
    return wParam & 0xffff;
}

void WindowSetFocus(Window &w) {
    Platform::SendScintilla(w.GetID(), SCI_GRABFOCUS, 0, 0);
}

void SciTEBase::SetLineNumberWidth() {
    if (lineNumbers) {
        int lineNumWidth = lineNumbersWidth;

        if (lineNumbersExpand) {
            // The margin size will be expanded if the current buffer's maximum
            // line number would overflow the margin.

            int lineCount = SendEditor(SCI_GETLINECOUNT);

            lineNumWidth = 1;
            while (lineCount >= 10) {
                lineCount /= 10;
                ++lineNumWidth;
            }

            if (lineNumWidth < lineNumbersWidth) {
                lineNumWidth = lineNumbersWidth;
            }
        }

        // The 4 here allows for spacing: 1 pixel on left and 3 on right.
        int pixelWidth = 4 + lineNumWidth * SendEditorString(SCI_TEXTWIDTH, STYLE_LINENUMBER, L"9");

        SendEditor(SCI_SETMARGINWIDTHN, 0, pixelWidth);
    } else {
        SendEditor(SCI_SETMARGINWIDTHN, 0, 0);
    }
}

void SciTEBase::MenuCommand(int cmdID, int source) {
    switch (cmdID) {
    case IDM_NEW:
        // For the New command, the "are you sure" question is always asked as this gives
        // an opportunity to abandon the edits made to a file when are.you.sure is turned off.
        if (CanMakeRoom()) {
            New();
            ReadProperties();
            SetIndentSettings();
            SetEol();
            UpdateStatusBar(true);
            WindowSetFocus(wEditor);
        }
        break;
    case IDM_OPEN:
        // No need to see if can make room as that will occur
        // when doing the opening. Must be done there as user
        // may decide to open multiple files so do not know yet
        // how much room needed.
        OpenDialog(filePath.Directory(), props.GetExpanded(L"open.filter").c_str());
        WindowSetFocus(wEditor);
        break;
    case IDM_OPENSELECTED:
        if (OpenSelected())
            WindowSetFocus(wEditor);
        break;
    case IDM_CLOSE:
        if (SaveIfUnsure() != IDCANCEL) {
            Close();
            WindowSetFocus(wEditor);
        }
        break;
    case IDM_CLOSEALL:
        CloseAllBuffers();
        break;
    case IDM_SAVE:
        Save();
        WindowSetFocus(wEditor);
        break;
    case IDM_SAVEALL:
        SaveAllBuffers(false, true);
        break;
    case IDM_SAVEAS:
        SaveAsDialog();
        WindowSetFocus(wEditor);
        break;
    case IDM_SAVEACOPY:
        SaveACopy();
        WindowSetFocus(wEditor);
        break;
    case IDM_SAVEASHTML:
        SaveAsHTML();
        WindowSetFocus(wEditor);
        break;
    case IDM_SAVEASRTF:
        SaveAsRTF();
        WindowSetFocus(wEditor);
        break;
    case IDM_SAVEASPDF:
        SaveAsPDF();
        WindowSetFocus(wEditor);
        break;
    case IDM_SAVEASTEX:
        SaveAsTEX();
        WindowSetFocus(wEditor);
        break;
    case IDM_SAVEASXML:
        SaveAsXML();
        WindowSetFocus(wEditor);
        break;
    case IDM_REVERT:
        Revert();
        WindowSetFocus(wEditor);
        break;
    case IDM_PRINT:
        Print(true);
        break;
    case IDM_PRINTSETUP:
        PrintSetup();
        break;
    case IDM_LOADSESSION:
        LoadSessionDialog();
        WindowSetFocus(wEditor);
        break;
    case IDM_SAVESESSION:
        SaveSessionDialog();
        WindowSetFocus(wEditor);
        break;
    case IDM_ABOUT:
        AboutDialog();
        break;
    case IDM_QUIT:
        QuitProgram();
        break;
    case IDM_ENCODING_DEFAULT:
		codePage = props.GetInt(L"code.page");
    case IDM_ENCODING_UCS2BE:
		codePage = SC_CP_UTF16_Big_Endian;
    case IDM_ENCODING_UCS2LE:
		codePage = SC_CP_UTF16_Little_Endian;
    case IDM_ENCODING_UTF8:
		codePage = SC_CP_UTF8_NO_BOM;
    case IDM_ENCODING_UCOOKIE:
		codePage = SC_CP_UTF8;
        CurrentBuffer()->codepage = codePage;
        SendEditor(SCI_SETCODEPAGE, codePage);
        break;

    case IDM_NEXTFILESTACK:
        if (buffers.size > 1 && props.GetInt(L"buffers.zorder.switching")) {
            NextInStack(); // next most recently selected buffer
            WindowSetFocus(wEditor);
            break;
        }
        // else fall through and do NEXTFILE behaviour...
    case IDM_NEXTFILE:
        if (buffers.size > 1) {
            Next(); // Use Next to tabs move left-to-right
            WindowSetFocus(wEditor);
        } else {
            // Not using buffers - switch to next file on MRU
            StackMenuNext();
        }
        break;

    case IDM_PREVFILESTACK:
        if (buffers.size > 1 && props.GetInt(L"buffers.zorder.switching")) {
            PrevInStack(); // next least recently selected buffer
            WindowSetFocus(wEditor);
            break;
        }
        // else fall through and do PREVFILE behaviour...
    case IDM_PREVFILE:
        if (buffers.size > 1) {
            Prev(); // Use Prev to tabs move right-to-left
            WindowSetFocus(wEditor);
        } else {
            // Not using buffers - switch to previous file on MRU
            StackMenuPrev();
        }
        break;

    case IDM_MOVETABRIGHT:
        MoveTabRight();
        WindowSetFocus(wEditor);
        break;
    case IDM_MOVETABLEFT:
        MoveTabLeft();
        WindowSetFocus(wEditor);
        break;

    case IDM_UNDO:
        SendPane(source, SCI_UNDO);
        CheckMenus();
        break;
    case IDM_REDO:
        SendPane(source, SCI_REDO);
        CheckMenus();
        break;

    case IDM_CUT:
        if (SendPane(source, SCI_GETSELECTIONSTART) != SendPane(source, SCI_GETSELECTIONEND)) {
            SendPane(source, SCI_CUT);
        }
        break;
    case IDM_COPY:
        if (SendPane(source, SCI_GETSELECTIONSTART) != SendPane(source, SCI_GETSELECTIONEND)) {
            //fprintf(stderr, "Copy from %d\n", source);
            SendPane(source, SCI_COPY);
        }
        // does not trigger SCN_UPDATEUI, so do CheckMenusClipboard() here
        CheckMenusClipboard();
        break;
    case IDM_PASTE:
        SendPane(source, SCI_PASTE);
        break;
    case IDM_DUPLICATE:
        SendPane(source, SCI_SELECTIONDUPLICATE);
        break;
    case IDM_PASTEANDDOWN: {
        int pos = SendFocused(SCI_GETCURRENTPOS);
        SendFocused(SCI_PASTE);
        SendFocused(SCI_SETCURRENTPOS, pos);
        SendFocused(SCI_CHARLEFT);
        SendFocused(SCI_LINEDOWN);
                           }
                           break;
    case IDM_CLEAR:
        SendPane(source, SCI_CLEAR);
        break;
    case IDM_SELECTALL:
        SendPane(source, SCI_SELECTALL);
        break;
    case IDM_COPYASRTF:
        CopyAsRTF();
        break;

    case IDM_FIND:
        Find();
        break;

    case IDM_INCSEARCH:
        IncrementSearchMode();
        break;

    case IDM_FINDNEXT:
        FindNext(reverseFind);
        break;

    case IDM_FINDNEXTBACK:
        FindNext(!reverseFind);
        break;

    case IDM_FINDNEXTSEL:
        SelectionIntoFind();
        FindNext(reverseFind);
        break;

    case IDM_ENTERSELECTION:
        SelectionIntoFind();
        break;

    case IDM_FINDNEXTBACKSEL:
        SelectionIntoFind();
        FindNext(!reverseFind);
        break;

    case IDM_FINDINFILES:
        FindInFiles();
        break;

    case IDM_REPLACE:
        Replace();
        break;

    case IDM_GOTO:
        GoLineDialog();
        break;

    case IDM_MATCHBRACE:
        GoMatchingBrace(false);
        break;

    case IDM_SELECTTOBRACE:
        GoMatchingBrace(true);
        break;

    case IDM_PREVMATCHPPC:
        GoMatchingPreprocCond(IDM_PREVMATCHPPC, false);
        break;

    case IDM_SELECTTOPREVMATCHPPC:
        GoMatchingPreprocCond(IDM_PREVMATCHPPC, true);
        break;

    case IDM_NEXTMATCHPPC:
        GoMatchingPreprocCond(IDM_NEXTMATCHPPC, false);
        break;

    case IDM_SELECTTONEXTMATCHPPC:
        GoMatchingPreprocCond(IDM_NEXTMATCHPPC, true);
        break;

    case IDM_SHOWCALLTIP:
        StartCallTip();
        break;

    case IDM_COMPLETE:
        autoCCausedByOnlyOne = false;
        StartAutoComplete();
        break;

    case IDM_COMPLETEWORD:
        autoCCausedByOnlyOne = false;
        StartAutoCompleteWord(false);
        break;

    case IDM_ABBREV:
        SendEditor(SCI_CANCEL);
        StartExpandAbbreviation();
        break;

    case IDM_INS_ABBREV:
        SendEditor(SCI_CANCEL);
        StartInsertAbbreviation();
        break;

    case IDM_BLOCK_COMMENT:
        StartBlockComment();
        break;

    case IDM_BOX_COMMENT:
        StartBoxComment();
        break;

    case IDM_STREAM_COMMENT:
        StartStreamComment();
        break;

    case IDM_TOGGLE_FOLDALL:
        FoldAll();
        break;

    case IDM_UPRCASE:
        SendFocused(SCI_UPPERCASE);
        break;

    case IDM_LWRCASE:
        SendFocused(SCI_LOWERCASE);
        break;

    case IDM_JOIN:
        SendFocused(SCI_TARGETFROMSELECTION);
        SendFocused(SCI_LINESJOIN);
        break;

    case IDM_SPLIT:
        SendFocused(SCI_TARGETFROMSELECTION);
        SendFocused(SCI_LINESSPLIT);
        break;

    case IDM_EXPAND:
        SendEditor(SCI_TOGGLEFOLD, GetCurrentLineNumber());
        break;

    case IDM_TOGGLE_FOLDRECURSIVE: {
        int line = GetCurrentLineNumber();
        int level = SendEditor(SCI_GETFOLDLEVEL, line);
        ToggleFoldRecursive(line, level);
                                   }
                                   break;

    case IDM_EXPAND_ENSURECHILDRENVISIBLE: {
        int line = GetCurrentLineNumber();
        int level = SendEditor(SCI_GETFOLDLEVEL, line);
        EnsureAllChildrenVisible(line, level);
                                           }
                                           break;

    case IDM_SPLITVERTICAL:
        splitVertical = !splitVertical;
        heightOutput = NormaliseSplit(heightOutput);
        SizeSubWindows();
        CheckMenus();
        Redraw();
        break;

    case IDM_LINENUMBERMARGIN:
        lineNumbers = !lineNumbers;
        SetLineNumberWidth();
        CheckMenus();
        break;

    case IDM_SELMARGIN:
        margin = !margin;
        SendEditor(SCI_SETMARGINWIDTHN, 1, margin ? marginWidth : 0);
        CheckMenus();
        break;

    case IDM_FOLDMARGIN:
        foldMargin = !foldMargin;
        SendEditor(SCI_SETMARGINWIDTHN, 2, foldMargin ? foldMarginWidth : 0);
        CheckMenus();
        break;

    case IDM_VIEWEOL:
        SendEditor(SCI_SETVIEWEOL, !SendEditor(SCI_GETVIEWEOL));
        CheckMenus();
        break;

    case IDM_VIEWTOOLBAR:
        tbVisible = !tbVisible;
        ShowToolBar();
        CheckMenus();
        break;

    case IDM_TOGGLEOUTPUT:
        ToggleOutputVisible();
        CheckMenus();
        break;

    case IDM_TOGGLEPARAMETERS:
        ParametersDialog(false);
        CheckMenus();
        break;

    case IDM_WRAP:
        wrap = !wrap;
        SendEditor(SCI_SETWRAPMODE, wrap ? wrapStyle : SC_WRAP_NONE);
        CheckMenus();
        break;

    case IDM_WRAPOUTPUT:
        wrapOutput = !wrapOutput;
        SendOutput(SCI_SETWRAPMODE, wrapOutput ? wrapStyle : SC_WRAP_NONE);
        CheckMenus();
        break;

    case IDM_READONLY:
        isReadOnly = !isReadOnly;
        SendEditor(SCI_SETREADONLY, isReadOnly);
        UpdateStatusBar(true);
        CheckMenus();
        break;

    case IDM_VIEWTABBAR:
        tabVisible = !tabVisible;
        ShowTabBar();
        CheckMenus();
        break;

    case IDM_VIEWSTATUSBAR:
        sbVisible = !sbVisible;
        ShowStatusBar();
        UpdateStatusBar(true);
        CheckMenus();
        break;

    case IDM_CLEAROUTPUT:
        SendOutputEx(SCI_CLEARALL, 0, 0, false);
        break;

    case IDM_SWITCHPANE:
        if (wEditor.HasFocus())
            WindowSetFocus(wOutput);
        else
            WindowSetFocus(wEditor);
        break;

    case IDM_EOL_CRLF:
        SendEditor(SCI_SETEOLMODE, SC_EOL_CRLF);
        CheckMenus();
        UpdateStatusBar(false);
        break;

    case IDM_EOL_CR:
        SendEditor(SCI_SETEOLMODE, SC_EOL_CR);
        CheckMenus();
        UpdateStatusBar(false);
        break;
    case IDM_EOL_LF:
        SendEditor(SCI_SETEOLMODE, SC_EOL_LF);
        CheckMenus();
        UpdateStatusBar(false);
        break;
    case IDM_EOL_CONVERT:
        SendEditor(SCI_CONVERTEOLS, SendEditor(SCI_GETEOLMODE));
        break;

    case IDM_VIEWSPACE:
        ViewWhitespace(!SendEditor(SCI_GETVIEWWS));
        CheckMenus();
        Redraw();
        break;

    case IDM_VIEWGUIDES: {
        bool viewIG = SendEditor(SCI_GETINDENTATIONGUIDES, 0, 0) == 0;
        SendEditor(SCI_SETINDENTATIONGUIDES, viewIG ? indentExamine : SC_IV_NONE);
        CheckMenus();
        Redraw();
                         }
                         break;

    case IDM_COMPILE: {
        if (SaveIfUnsureForBuilt() != IDCANCEL) {
            SelectionIntoProperties();
            AddCommand(props.GetWild(L"command.compile.", FileNameExt().AsInternal()), L"",
                SubsystemType(L"command.compile.subsystem."));
            if (jobQueue.commandCurrent > 0)
                Execute();
        }
                      }
                      break;

    case IDM_BUILD: {
        if (SaveIfUnsureForBuilt() != IDCANCEL) {
            SelectionIntoProperties();
            AddCommand(
                props.GetWild(L"command.build.", FileNameExt().AsInternal()),
                props.GetNewExpand(L"command.build.directory.", FileNameExt().AsInternal()),
                SubsystemType(L"command.build.subsystem."));
            if (jobQueue.commandCurrent > 0) {
                jobQueue.isBuilding = true;
                Execute();
            }
        }
                    }
                    break;

    case IDM_GO: {
        if (SaveIfUnsureForBuilt() != IDCANCEL) {
            SelectionIntoProperties();
            long flags = 0;

            if (!jobQueue.isBuilt) {
                SString buildcmd = props.GetNewExpand(L"command.go.needs.", FileNameExt().AsInternal());
                AddCommand(buildcmd, L"",
                    SubsystemType(L"command.go.needs.subsystem."));
                if (buildcmd.length() > 0) {
                    jobQueue.isBuilding = true;
                    flags |= jobForceQueue;
                }
            }
            AddCommand(props.GetWild(L"command.go.", FileNameExt().AsInternal()), L"",
                SubsystemType(L"command.go.subsystem."), L"", flags);
            if (jobQueue.commandCurrent > 0)
                Execute();
        }
                 }
                 break;

    case IDM_STOPEXECUTE:
        StopExecute();
        break;

    case IDM_NEXTMSG:
        GoMessage(1);
        break;

    case IDM_PREVMSG:
        GoMessage(-1);
        break;

    case IDM_OPENLOCALPROPERTIES:
        OpenProperties(IDM_OPENLOCALPROPERTIES);
        WindowSetFocus(wEditor);
        break;

    case IDM_OPENUSERPROPERTIES:
        OpenProperties(IDM_OPENUSERPROPERTIES);
        WindowSetFocus(wEditor);
        break;

    case IDM_OPENGLOBALPROPERTIES:
        OpenProperties(IDM_OPENGLOBALPROPERTIES);
        WindowSetFocus(wEditor);
        break;

    case IDM_OPENABBREVPROPERTIES:
        OpenProperties(IDM_OPENABBREVPROPERTIES);
        WindowSetFocus(wEditor);
        break;

    case IDM_OPENLUAEXTERNALFILE:
        OpenProperties(IDM_OPENLUAEXTERNALFILE);
        WindowSetFocus(wEditor);
        break;

    case IDM_OPENDIRECTORYPROPERTIES:
        OpenProperties(IDM_OPENDIRECTORYPROPERTIES);
        WindowSetFocus(wEditor);
        break;

    case IDM_SRCWIN:
        break;

    case IDM_BOOKMARK_TOGGLE:
        BookmarkToggle();
        break;

    case IDM_BOOKMARK_NEXT:
        BookmarkNext(true);
        break;

    case IDM_BOOKMARK_PREV:
        BookmarkNext(false);
        break;

    case IDM_BOOKMARK_NEXT_SELECT:
        BookmarkNext(true, true);
        break;

    case IDM_BOOKMARK_PREV_SELECT:
        BookmarkNext(false, true);
        break;

    case IDM_BOOKMARK_CLEARALL:
        SendEditor(SCI_MARKERDELETEALL, markerBookmark);
        RemoveFindMarks();
        break;

    case IDM_TABSIZE:
        TabSizeDialog();
        break;

    case IDM_MONOFONT:
        CurrentBuffer()->useMonoFont = !CurrentBuffer()->useMonoFont;
        ReadFontProperties();
        Redraw();
        break;

    case IDM_MACROLIST:
        AskMacroList();
        break;
    case IDM_MACROPLAY:
        StartPlayMacro();
        break;
    case IDM_MACRORECORD:
        StartRecordMacro();
        break;
    case IDM_MACROSTOPRECORD:
        StopRecordMacro();
        break;

    case IDM_HELP: {
        SelectionIntoProperties();
        AddCommand(props.GetWild(L"command.help.", FileNameExt().AsInternal()), L"",
            SubsystemType(L"command.help.subsystem."));
        if (jobQueue.commandCurrent > 0) {
            jobQueue.isBuilding = true;
            Execute();
        }
                   }
                   break;

    case IDM_HELP_SCITE: {
        SelectionIntoProperties();
        AddCommand(props.Get(L"command.scite.help"), L"",
            SubsystemType(props.Get(L"command.scite.help.subsystem")[0]));
        if (jobQueue.commandCurrent > 0) {
            jobQueue.isBuilding = true;
            Execute();
        }
                         }
                         break;

    default:
        if ((cmdID >= bufferCmdID) &&
            (cmdID < bufferCmdID + buffers.size)) {
                SetDocumentAt(cmdID - bufferCmdID);
                CheckReload();
        } else if ((cmdID >= fileStackCmdID) &&
            (cmdID < fileStackCmdID + fileStackMax)) {
                StackMenu(cmdID - fileStackCmdID);
        } else if (cmdID >= importCmdID &&
            (cmdID < importCmdID + importMax)) {
                ImportMenu(cmdID - importCmdID);
        } else if (cmdID >= IDM_TOOLS && cmdID < IDM_TOOLS + toolMax) {
            ToolsMenu(cmdID - IDM_TOOLS);
        } else if (cmdID >= IDM_LANGUAGE && cmdID < IDM_LANGUAGE + 100) {
            SetOverrideLanguage(cmdID - IDM_LANGUAGE);
        }
        break;
    }
}

void SciTEBase::FoldChanged(int line, int levelNow, int levelPrev) {
    //Platform::DebugPrintf("Fold %d %x->%x\n", line, levelPrev, levelNow);
    if (levelNow & SC_FOLDLEVELHEADERFLAG) {
        if (!(levelPrev & SC_FOLDLEVELHEADERFLAG)) {
            // Adding a fold point.
            SendEditor(SCI_SETFOLDEXPANDED, line, 1);
            Expand(line, true, false, 0, levelPrev);
        }
    } else if (levelPrev & SC_FOLDLEVELHEADERFLAG) {
        //Platform::DebugPrintf("Fold removed %d-%d\n", line, SendEditor(SCI_GETLASTCHILD, line));
        if (!SendEditor(SCI_GETFOLDEXPANDED, line)) {
            // Removing the fold from one that has been contracted so should expand
            // otherwise lines are left invisible with no way to make them visible
            SendEditor(SCI_SETFOLDEXPANDED, line, 1);
            Expand(line, true, false, 0, levelPrev);
        }
    }
    if (!(levelNow & SC_FOLDLEVELWHITEFLAG) &&
        ((levelPrev & SC_FOLDLEVELNUMBERMASK) > (levelNow & SC_FOLDLEVELNUMBERMASK))) {
            // See if should still be hidden
            int parentLine = SendEditor(SCI_GETFOLDPARENT, line);
            if (parentLine < 0) {
                SendEditor(SCI_SHOWLINES, line, line);
            } else if (SendEditor(SCI_GETFOLDEXPANDED, parentLine) && SendEditor(SCI_GETLINEVISIBLE, parentLine)) {
                SendEditor(SCI_SHOWLINES, line, line);
            }
    }
}

void SciTEBase::Expand(int &line, bool doExpand, bool force, int visLevels, int level) {
    int lineMaxSubord = SendEditor(SCI_GETLASTCHILD, line, level & SC_FOLDLEVELNUMBERMASK);
    line++;
    while (line <= lineMaxSubord) {
        if (force) {
            if (visLevels > 0)
                SendEditor(SCI_SHOWLINES, line, line);
            else
                SendEditor(SCI_HIDELINES, line, line);
        } else {
            if (doExpand)
                SendEditor(SCI_SHOWLINES, line, line);
        }
        int levelLine = level;
        if (levelLine == -1)
            levelLine = SendEditor(SCI_GETFOLDLEVEL, line);
        if (levelLine & SC_FOLDLEVELHEADERFLAG) {
            if (force) {
                if (visLevels > 1)
                    SendEditor(SCI_SETFOLDEXPANDED, line, 1);
                else
                    SendEditor(SCI_SETFOLDEXPANDED, line, 0);
                Expand(line, doExpand, force, visLevels - 1);
            } else {
                if (doExpand) {
                    if (!SendEditor(SCI_GETFOLDEXPANDED, line))
                        SendEditor(SCI_SETFOLDEXPANDED, line, 1);
                    Expand(line, true, force, visLevels - 1);
                } else {
                    Expand(line, false, force, visLevels - 1);
                }
            }
        } else {
            line++;
        }
    }
}

void SciTEBase::FoldAll() {
    SendEditor(SCI_COLOURISE, 0, -1);
    int maxLine = SendEditor(SCI_GETLINECOUNT);
    bool expanding = true;
    for (int lineSeek = 0; lineSeek < maxLine; lineSeek++) {
        if (SendEditor(SCI_GETFOLDLEVEL, lineSeek) & SC_FOLDLEVELHEADERFLAG) {
            expanding = !SendEditor(SCI_GETFOLDEXPANDED, lineSeek);
            break;
        }
    }
    for (int line = 0; line < maxLine; line++) {
        int level = SendEditor(SCI_GETFOLDLEVEL, line);
        if ((level & SC_FOLDLEVELHEADERFLAG) &&
            (SC_FOLDLEVELBASE == (level & SC_FOLDLEVELNUMBERMASK))) {
                if (expanding) {
                    SendEditor(SCI_SETFOLDEXPANDED, line, 1);
                    Expand(line, true, false, 0, level);
                    line--;
                } else {
                    int lineMaxSubord = SendEditor(SCI_GETLASTCHILD, line, -1);
                    SendEditor(SCI_SETFOLDEXPANDED, line, 0);
                    if (lineMaxSubord > line)
                        SendEditor(SCI_HIDELINES, line + 1, lineMaxSubord);
                }
        }
    }
}

void SciTEBase::GotoLineEnsureVisible(int line) {
    SendEditor(SCI_ENSUREVISIBLEENFORCEPOLICY, line);
    SendEditor(SCI_GOTOLINE, line);
}

void SciTEBase::EnsureRangeVisible(int posStart, int posEnd, bool enforcePolicy) {
    int lineStart = SendEditor(SCI_LINEFROMPOSITION, Platform::Minimum(posStart, posEnd));
    int lineEnd = SendEditor(SCI_LINEFROMPOSITION, Platform::Maximum(posStart, posEnd));
    for (int line = lineStart; line <= lineEnd; line++) {
        SendEditor(enforcePolicy ? SCI_ENSUREVISIBLEENFORCEPOLICY : SCI_ENSUREVISIBLE, line);
    }
}

bool SciTEBase::MarginClick(int position, int modifiers) {
    int lineClick = SendEditor(SCI_LINEFROMPOSITION, position);
    //Platform::DebugPrintf("Margin click %d %d %x\n", position, lineClick,
    //	SendEditor(SCI_GETFOLDLEVEL, lineClick) & SC_FOLDLEVELHEADERFLAG);
    if ((modifiers & SCMOD_SHIFT) && (modifiers & SCMOD_CTRL)) {
        FoldAll();
    } else {
        int levelClick = SendEditor(SCI_GETFOLDLEVEL, lineClick);
        if (levelClick & SC_FOLDLEVELHEADERFLAG) {
            if (modifiers & SCMOD_SHIFT) {
                EnsureAllChildrenVisible(lineClick, levelClick);
            } else if (modifiers & SCMOD_CTRL) {
                ToggleFoldRecursive(lineClick, levelClick);
            } else {
                // Toggle this line
                SendEditor(SCI_TOGGLEFOLD, lineClick);
            }
        }
    }
    return true;
}

void SciTEBase::ToggleFoldRecursive(int line, int level) {
    if (SendEditor(SCI_GETFOLDEXPANDED, line)) {
        // Contract this line and all children
        SendEditor(SCI_SETFOLDEXPANDED, line, 0);
        Expand(line, false, true, 0, level);
    } else {
        // Expand this line and all children
        SendEditor(SCI_SETFOLDEXPANDED, line, 1);
        Expand(line, true, true, 100, level);
    }
}

void SciTEBase::EnsureAllChildrenVisible(int line, int level) {
    // Ensure all children visible
    SendEditor(SCI_SETFOLDEXPANDED, line, 1);
    Expand(line, true, true, 100, level);
}

void SciTEBase::NewLineInOutput() {
    if (jobQueue.IsExecuting())
        return;
    int line = SendOutput(SCI_LINEFROMPOSITION,
        SendOutput(SCI_GETCURRENTPOS)) - 1;
    SString cmd = GetLine(wOutput, line);
    if (cmd == L">") {
        // Search output buffer for previous command
        line--;
        while (line >= 0) {
            cmd = GetLine(wOutput, line);
            if (cmd.startswith(L">") && !cmd.startswith(L">Exit")) {
                cmd = cmd.substr(1);
                break;
            }
            line--;
        }
    } else if (cmd.startswith(L">")) {
        cmd = cmd.substr(1);
    }
    returnOutputToCommand = false;
    AddCommand(cmd, L".", jobCLI);
    Execute();
}

void SciTEBase::Notify(SCNotification *notification) {
    bool handled = false;
    //Platform::DebugPrintf("Notify %d\n", notification->nmhdr.code);
    switch (notification->nmhdr.code) {
    case SCEN_SETFOCUS:
    case SCEN_KILLFOCUS:
        CheckMenus();
        break;

    case SCN_STYLENEEDED: {
        if (extender) {
            // Colourisation may be performed by script
            if ((notification->nmhdr.idFrom == IDM_SRCWIN) && (lexLanguage == SCLEX_CONTAINER)) {
                int endStyled = SendEditor(SCI_GETENDSTYLED);
                int lineEndStyled = SendEditor(SCI_LINEFROMPOSITION, endStyled);
                endStyled = SendEditor(SCI_POSITIONFROMLINE, lineEndStyled);
                WindowAccessor styler(wEditor.GetID(), props);
                int styleStart = 0;
                if (endStyled > 0)
                    styleStart = styler.StyleAt(endStyled - 1);
                styler.SetCodePage(codePage);
                extender->OnStyle(endStyled, notification->position - endStyled,
                    styleStart, &styler);
                styler.Flush();
            }
        }
                          }
                          break;

    case SCN_CHARADDED:
        if (extender)
            handled = extender->OnChar(static_cast<WCHAR>(notification->ch));
        if (!handled) {
            if (notification->nmhdr.idFrom == IDM_SRCWIN) {
                CharAdded(static_cast<WCHAR>(notification->ch));
            } else {
                CharAddedOutput(notification->ch);
            }
        }
        break;

    case SCN_SAVEPOINTREACHED:
        if (notification->nmhdr.idFrom == IDM_SRCWIN) {
            if (extender)
                handled = extender->OnSavePointReached();
            if (!handled) {
                CurrentBuffer()->isDirty = false;
            }
        }
        CheckMenus();
        SetWindowName();
        BuffersMenu();
        break;

    case SCN_SAVEPOINTLEFT:
        if (notification->nmhdr.idFrom == IDM_SRCWIN) {
            if (extender)
                handled = extender->OnSavePointLeft();
            if (!handled) {
                CurrentBuffer()->isDirty = true;
                jobQueue.isBuilt = false;
            }
        }
        CheckMenus();
        SetWindowName();
        BuffersMenu();
        break;

    case SCN_DOUBLECLICK:
        if (extender)
            handled = extender->OnDoubleClick();
        if (!handled && notification->nmhdr.idFrom == IDM_RUNWIN) {
            GoMessage(0);
        }
        break;

    case SCN_UPDATEUI:
        if (extender)
            handled = extender->OnUpdateUI();
        if (!handled) {
            BraceMatch(notification->nmhdr.idFrom == IDM_SRCWIN);
            if (notification->nmhdr.idFrom == IDM_SRCWIN) {
                UpdateStatusBar(false);
            }
            CheckMenusClipboard();
        }
        if (CurrentBuffer()->findMarks == Buffer::fmModified) {
            RemoveFindMarks();
        }
        break;

    case SCN_MODIFIED:
        if (notification->modificationType & SC_LASTSTEPINUNDOREDO) {
            //when the user hits undo or redo, several normal insert/delete
            //notifications may fire, but we will end up here in the end
            EnableAMenuItem(IDM_UNDO, SendFocused(SCI_CANUNDO));
            EnableAMenuItem(IDM_REDO, SendFocused(SCI_CANREDO));
        } else if (notification->modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT)) {
            //this will be called a lot, and usually means "typing".
            EnableAMenuItem(IDM_UNDO, TRUE);
            EnableAMenuItem(IDM_REDO, FALSE);
            if (CurrentBuffer()->findMarks == Buffer::fmMarked) {
                CurrentBuffer()->findMarks = Buffer::fmModified;
            }
        }

        if (notification->linesAdded && lineNumbers && lineNumbersExpand)
            SetLineNumberWidth();

        if (0 != (notification->modificationType & SC_MOD_CHANGEFOLD)) {
            FoldChanged(notification->line,
                notification->foldLevelNow, notification->foldLevelPrev);
        }
        break;

    case SCN_MARGINCLICK: {
        if (extender)
            handled = extender->OnMarginClick();
        if (!handled) {
            if (notification->margin == 2) {
                MarginClick(notification->position, notification->modifiers);
            }
        }
                          }
                          break;

    case SCN_NEEDSHOWN: {
        EnsureRangeVisible(notification->position, notification->position + notification->length, false);
                        }
                        break;

    case SCN_USERLISTSELECTION: {
        if (notification->wParam == 2)
            ContinueMacroList(notification->text);
        else if (extender && notification->wParam > 2)
            extender->OnUserListSelection(notification->wParam, notification->text);
                                }
                                break;

    case SCN_CALLTIPCLICK: {
        if (notification->position == 1 && currentCallTip > 0) {
            currentCallTip--;
            FillFunctionDefinition();
        } else if (notification->position == 2 && currentCallTip + 1 < maxCallTips) {
            currentCallTip++;
            FillFunctionDefinition();
        }
                           }
                           break;

    case SCN_MACRORECORD:
        RecordMacroCommand(notification);
        break;

    case SCN_URIDROPPED:
        OpenUriList(notification->text);
        break;

    case SCN_DWELLSTART:
        if (extender && (INVALID_POSITION != notification->position)) {
            int endWord = notification->position;
            SString message =
                RangeExtendAndGrab(wEditor,
                notification->position, endWord, &SciTEBase::iswordcharforsel);
            if (message.length()) {
                extender->OnDwellStart(notification->position,message.c_str());
            }
        }
        break;

    case SCN_DWELLEND:
        if (extender) {
            extender->OnDwellStart(0,L""); // flags end of calltip
        }
        break;

    case SCN_ZOOM:
        SetLineNumberWidth();
        break;
    }
}

void SciTEBase::CheckMenusClipboard() {
    bool hasSelection = SendFocused(SCI_GETSELECTIONSTART) != SendFocused(SCI_GETSELECTIONEND);
    EnableAMenuItem(IDM_CUT, hasSelection);
    EnableAMenuItem(IDM_COPY, hasSelection);
    EnableAMenuItem(IDM_CLEAR, hasSelection);
    EnableAMenuItem(IDM_PASTE, SendFocused(SCI_CANPASTE));
}

void SciTEBase::CheckMenus() {
    CheckMenusClipboard();
    EnableAMenuItem(IDM_SAVE, CurrentBuffer()->isDirty);
    EnableAMenuItem(IDM_UNDO, SendFocused(SCI_CANUNDO));
    EnableAMenuItem(IDM_REDO, SendFocused(SCI_CANREDO));
    EnableAMenuItem(IDM_DUPLICATE, !isReadOnly);
    EnableAMenuItem(IDM_FINDINFILES, !jobQueue.IsExecuting());
    EnableAMenuItem(IDM_SHOWCALLTIP, apis != 0);
    EnableAMenuItem(IDM_COMPLETE, apis != 0);
    CheckAMenuItem(IDM_SPLITVERTICAL, splitVertical);
    EnableAMenuItem(IDM_OPENFILESHERE, props.GetInt(L"check.if.already.open") != 0);
    CheckAMenuItem(IDM_OPENFILESHERE, openFilesHere);
    CheckAMenuItem(IDM_WRAP, wrap);
    CheckAMenuItem(IDM_WRAPOUTPUT, wrapOutput);
    CheckAMenuItem(IDM_READONLY, isReadOnly);
    CheckAMenuItem(IDM_FULLSCREEN, fullScreen);
    CheckAMenuItem(IDM_VIEWTOOLBAR, tbVisible);
    CheckAMenuItem(IDM_VIEWTABBAR, tabVisible);
    CheckAMenuItem(IDM_VIEWSTATUSBAR, sbVisible);
    CheckAMenuItem(IDM_VIEWEOL, SendEditor(SCI_GETVIEWEOL));
    CheckAMenuItem(IDM_VIEWSPACE, SendEditor(SCI_GETVIEWWS));
    CheckAMenuItem(IDM_VIEWGUIDES, SendEditor(SCI_GETINDENTATIONGUIDES));
    CheckAMenuItem(IDM_LINENUMBERMARGIN, lineNumbers);
    CheckAMenuItem(IDM_SELMARGIN, margin);
    CheckAMenuItem(IDM_FOLDMARGIN, foldMargin);
    CheckAMenuItem(IDM_TOGGLEOUTPUT, heightOutput > 0);
    CheckAMenuItem(IDM_TOGGLEPARAMETERS, ParametersOpen());
    CheckAMenuItem(IDM_MONOFONT, CurrentBuffer()->useMonoFont);
    EnableAMenuItem(IDM_COMPILE, !jobQueue.IsExecuting() &&
        props.GetWild(L"command.compile.", FileNameExt().AsInternal()).size() != 0);
    EnableAMenuItem(IDM_BUILD, !jobQueue.IsExecuting() &&
        props.GetWild(L"command.build.", FileNameExt().AsInternal()).size() != 0);
    EnableAMenuItem(IDM_GO, !jobQueue.IsExecuting() &&
        props.GetWild(L"command.go.", FileNameExt().AsInternal()).size() != 0);
    EnableAMenuItem(IDM_OPENDIRECTORYPROPERTIES, props.GetInt(L"properties.directory.enable") != 0);
    for (int toolItem = 0; toolItem < toolMax; toolItem++)
        EnableAMenuItem(IDM_TOOLS + toolItem, !jobQueue.IsExecuting());
    EnableAMenuItem(IDM_STOPEXECUTE, jobQueue.IsExecuting());
    if (buffers.size > 0) {
#if PLAT_WIN
        // Tab Bar
#ifndef TCM_DESELECTALL
#define TCM_DESELECTALL TCM_FIRST+50
#endif
        if (wTabBar.GetID()) {
            ::SendMessage(reinterpret_cast<HWND>(wTabBar.GetID()), TCM_DESELECTALL, (WPARAM)0, (LPARAM)0);
            ::SendMessage(reinterpret_cast<HWND>(wTabBar.GetID()), TCM_SETCURSEL, (WPARAM)buffers.Current(), (LPARAM)0);
        }
#endif
#if PLAT_GTK

        if (wTabBar.GetID())
            gtk_notebook_set_page(GTK_NOTEBOOK(wTabBar.GetID()), buffers.Current());
#endif

        for (int bufferItem = 0; bufferItem < buffers.length; bufferItem++) {
            CheckAMenuItem(IDM_BUFFER + bufferItem, bufferItem == buffers.Current());
        }
    }
    EnableAMenuItem(IDM_MACROPLAY, !recording);
    EnableAMenuItem(IDM_MACRORECORD, !recording);
    EnableAMenuItem(IDM_MACROSTOPRECORD, recording);
}

void SciTEBase::ContextMenu(Window wSource, Point pt, Window wCmd) {
    int currentPos = SendWindow(wSource, SCI_GETCURRENTPOS);
    int anchor = SendWindow(wSource, SCI_GETANCHOR);
    popup.CreatePopUp();
    bool writable = !SendWindow(wSource, SCI_GETREADONLY);
    AddToPopUp(L"Undo", IDM_UNDO, writable && SendWindow(wSource, SCI_CANUNDO));
    AddToPopUp(L"Redo", IDM_REDO, writable && SendWindow(wSource, SCI_CANREDO));
    AddToPopUp(L"");
    AddToPopUp(L"Cut", IDM_CUT, writable && currentPos != anchor);
    AddToPopUp(L"Copy", IDM_COPY, currentPos != anchor);
    AddToPopUp(L"Paste", IDM_PASTE, writable && SendWindow(wSource, SCI_CANPASTE));
    AddToPopUp(L"Delete", IDM_CLEAR, writable && currentPos != anchor);
    AddToPopUp(L"");
    AddToPopUp(L"Select All", IDM_SELECTALL);
    AddToPopUp(L"");
    if (wSource.GetID() == wOutput.GetID()) {
        AddToPopUp(L"Hide", IDM_TOGGLEOUTPUT, true);
    } else {
        AddToPopUp(L"Close", IDM_CLOSE, true);
    }
    SString userContextMenu = props.GetNewExpand(L"user.context.menu");
    userContextMenu.substitute(L'|', L'\0');
    const WCHAR *userContextItem = userContextMenu.c_str();
    const WCHAR *endDefinition = userContextItem + userContextMenu.length();
    while (userContextItem < endDefinition) {
        const WCHAR *caption = userContextItem;
        userContextItem += wcslen(userContextItem) + 1;
        if (userContextItem < endDefinition) {
            int cmd = GetMenuCommandAsInt(userContextItem);
            userContextItem += wcslen(userContextItem) + 1;
            AddToPopUp(caption, cmd);
        }
    }
    popup.Show(pt, wCmd);
}

/**
* Ensure that a splitter bar position is inside the main window.
*/
int SciTEBase::NormaliseSplit(int splitPos) {
    PRectangle rcClient = GetClientRectangle();
    int w = rcClient.Width();
    int h = rcClient.Height();
    if (splitPos < 20)
        splitPos = 0;
    if (splitVertical) {
        if (splitPos > w - heightBar - 20)
            splitPos = w - heightBar;
    } else {
        if (splitPos > h - heightBar - 20)
            splitPos = h - heightBar;
    }
    return splitPos;
}

void SciTEBase::MoveSplit(Point ptNewDrag) {
    int newHeightOutput = heightOutputStartDrag + (ptStartDrag.y - ptNewDrag.y);
    if (splitVertical)
        newHeightOutput = heightOutputStartDrag + (ptStartDrag.x - ptNewDrag.x);
    newHeightOutput = NormaliseSplit(newHeightOutput);
    if (heightOutput != newHeightOutput) {
        heightOutput = newHeightOutput;
        SizeContentWindows();
        //Redraw();
    }

    previousHeightOutput = newHeightOutput;
}

void SciTEBase::UIAvailable() {
    SetImportMenu();
    if (extender) {
        FilePath homepath = GetSciteDefaultHome();
        props.Set(L"SciteDefaultHome", homepath.AsFileSystem());
        homepath = GetSciteUserHome();
        props.Set(L"SciteUserHome", homepath.AsFileSystem());
        extender->Initialise(this);
    }
}

/**
* Find the character following a name which is made up of characters from
* the set [a-zA-Z.]
*/
static WCHAR AfterName(const WCHAR *s) {
    while (*s && ((*s == L'.') ||
        (*s >= L'a' && *s <= L'z') ||
        (*s >= L'A' && *s <= L'Z')))
        s++;
    return *s;
}

void SciTEBase::PerformOne(WCHAR *action) {
    unsigned int len = UnSlash(action);
    WCHAR *arg = wcschr(action, L':');
    if (arg) {
        arg++;
        if (isprefix(action, L"askfilename:")) {
            extender->OnMacro(L"filename", filePath.AsFileSystem());
        } else if (isprefix(action, L"askproperty:")) {
            PropertyToDirector(arg);
        } else if (isprefix(action, L"close:")) {
            Close();
            WindowSetFocus(wEditor);
        } else if (isprefix(action, L"currentmacro:")) {
            currentMacro = arg;
        } else if (isprefix(action, L"cwd:")) {
            if (!::SetCurrentDirectory(arg)){
            //if (_wchdir(arg) != 0) {
                SString msg = LocaliseMessage(L"Invalid directory '^0'.", arg);
                WindowMessageBox(wSciTE, msg, MB_OK | MB_ICONWARNING);
            }
        } else if (isprefix(action, L"enumproperties:")) {
            EnumProperties(arg);
        } else if (isprefix(action, L"exportashtml:")) {
            SaveToHTML(arg);
        } else if (isprefix(action, L"exportasrtf:")) {
            SaveToRTF(arg);
        } else if (isprefix(action, L"exportaspdf:")) {
            SaveToPDF(arg);
        } else if (isprefix(action, L"exportaslatex:")) {
            SaveToTEX(arg);
        } else if (isprefix(action, L"exportasxml:")) {
            SaveToXML(arg);
        } else if (isprefix(action, L"find:") && fnEditor) {
            findWhat = arg;
            FindNext(false, false);
        } else if (isprefix(action, L"goto:") && fnEditor) {
            int line = _wtoi(arg) - 1;
            GotoLineEnsureVisible(line);
            // jump to column if given and greater than 0
            WCHAR *colstr = wcschr(arg, L',');
            if (colstr != NULL) {
                int col = _wtoi(colstr + 1);
                if (col > 0) {
                    int pos = SendEditor(SCI_GETCURRENTPOS) + col;
                    // select the word you have found there
                    int wordStart = SendEditor(SCI_WORDSTARTPOSITION, pos, true);
                    int wordEnd = SendEditor(SCI_WORDENDPOSITION, pos, true);
                    SendEditor(SCI_SETSEL, wordStart, wordEnd);
                }
            }
        } else if (isprefix(action, L"insert:") && fnEditor) {
            SendEditorString(SCI_REPLACESEL, 0, arg);
        } else if (isprefix(action, L"loadsession:")) {
            if (*arg) {
                LoadSessionFile(arg);
                RestoreSession();
            }
        } else if (isprefix(action, L"macrocommand:")) {
            ExecuteMacroCommand(arg);
        } else if (isprefix(action, L"macroenable:")) {
            macrosEnabled = _wtoi(arg);
            SetToolsMenu();
        } else if (isprefix(action, L"macrolist:")) {
            StartMacroList(arg);
        } else if (isprefix(action, L"menucommand:")) {
            MenuCommand(_wtoi(arg));
        } else if (isprefix(action, L"open:")) {
            Open(arg);
        } else if (isprefix(action, L"output:") && fnOutput) {
            SendOutput(SCI_REPLACESEL, 0, reinterpret_cast<sptr_t>(arg));
        } else if (isprefix(action, L"property:")) {
            PropertyFromDirector(arg);
		} else if (isprefix(action, L"reloadproperties:")) {
            ReloadProperties();
        } else if (isprefix(action, L"quit:")) {
            QuitProgram();
        } else if (isprefix(action, L"replaceall:") && fnEditor) {
            if (len > wcslen(action)) {
                WCHAR *arg2 = arg + wcslen(arg) + 1;
                findWhat = arg;
                replaceWhat = arg2;
                ReplaceAll(false);
            }
        } else if (isprefix(action, L"saveas:")) {
            if (*arg) {
                SaveAs(arg, true);
            } else {
                SaveAsDialog();
            }
        } else if (isprefix(action, L"savesession:")) {
            if (*arg) {
                SaveSessionFile(arg);
            }
        } else if (isprefix(action, L"extender:")) {
            extender->OnExecute(arg);
        } else if (isprefix(action, L"focus:")) {
            ActivateWindow(arg);
        }
    }
}

static bool IsSwitchCharacter(WCHAR ch) {
#ifdef unix
    return ch == '-';
#else

    return (ch == L'-') || (ch == L'/');
#endif
}

// Called by SciTEBase::PerformOne when action="enumproperties:"
void SciTEBase::EnumProperties(const WCHAR *propkind) {
    const WCHAR *key = NULL;
    const WCHAR *val = NULL;
    PropSetFile *pf = NULL;

    if (!extender)
        return;
    if (!wcscmp(propkind, L"dyn")) {
        SelectionIntoProperties(); // Refresh properties ...
        pf = &props;
    } else if (!wcscmp(propkind, L"local"))
        pf = &propsLocal;
    else if (!wcscmp(propkind, L"directory"))
        pf = &propsDirectory;
    else if (!wcscmp(propkind, L"user"))
        pf = &propsUser;
    else if (!wcscmp(propkind, L"base"))
        pf = &propsBase;
    else if (!wcscmp(propkind, L"embed"))
        pf = &propsEmbed;
    else if (!wcscmp(propkind, L"abbrev"))
        pf = &propsAbbrev;

    if (pf != NULL) {
        bool b = pf->GetFirst(key, val);
        while (b) {
            SendOneProperty(propkind, key, val);
            b = pf->GetNext(key, val);
        }
    }
}

void SciTEBase::SendOneProperty(const WCHAR *kind, const WCHAR *key, const WCHAR *val) {
    size_t keysize = wcslen(kind) + 1 + wcslen(key) + 1 + wcslen(val) + 1;
    WCHAR *m = new WCHAR[keysize];
    wcscpy(m, kind);
    wcscat(m, L":");
    wcscat(m, key);
    wcscat(m, L"=");
    wcscat(m, val);
    extender->SendProperty(m);
    delete []m;
}

void SciTEBase::PropertyFromDirector(const WCHAR *arg) {
    props.Set(arg);
}

void SciTEBase::PropertyToDirector(const WCHAR *arg) {
    if (!extender)
        return;
    SelectionIntoProperties();
    SString gotprop = props.Get(arg);
    extender->OnMacro(L"macro:stringinfo", gotprop.c_str());
}

/**
* Menu/Toolbar command "Record".
*/
void SciTEBase::StartRecordMacro() {
    recording = true;
    CheckMenus();
    SendEditor(SCI_STARTRECORD);
}

/**
* Received a SCN_MACRORECORD from Scintilla: send it to director.
*/
bool SciTEBase::RecordMacroCommand(SCNotification *notification) {
    if (extender) {
        WCHAR *szMessage;
        WCHAR *t;
        bool handled;
        t = (WCHAR*)(notification->lParam);
        if (t != NULL) {
            //format : "<message>;<wParam>;1;<text>"
            szMessage = new WCHAR[50 + wcslen(t) + 4];
            wsprintf(szMessage, L"%d;%ld;1;%s", notification->message, notification->wParam, t);
        } else {
            //format : "<message>;<wParam>;0;"
            szMessage = new WCHAR[50];
            wsprintf(szMessage, L"%d;%ld;0;", notification->message, notification->wParam);
        }
        handled = extender->OnMacro(L"macro:record", szMessage);
        delete []szMessage;
        return handled;
    }
    return true;
}

/**
* Menu/Toolbar command "Stop recording".
*/
void SciTEBase::StopRecordMacro() {
    SendEditor(SCI_STOPRECORD);
    if (extender)
        extender->OnMacro(L"macro:stoprecord", L"");
    recording = false;
    CheckMenus();
}

/**
* Menu/Toolbar command "Play macro...": tell director to build list of Macro names
* Through this call, user has access to all macros in Filerx.
*/
void SciTEBase::AskMacroList() {
    if (extender)
        extender->OnMacro(L"macro:getlist", L"");
}

/**
* List of Macro names has been created. Ask Scintilla to show it.
*/
bool SciTEBase::StartMacroList(const WCHAR *words) {
    if (words) {
        SendEditorString(SCI_USERLISTSHOW, 2, words); //listtype=2
    }

    return true;
}

/**
* User has chosen a macro in the list. Ask director to execute it.
*/
void SciTEBase::ContinueMacroList(const WCHAR *stext) {
    if ((extender) && (*stext != L'\0')) {
        currentMacro = stext;
        StartPlayMacro();
    }
}

/**
* Menu/Toolbar command "Play current macro" (or called from ContinueMacroList).
*/
void SciTEBase::StartPlayMacro() {
    if (extender)
        extender->OnMacro(L"macro:run", currentMacro.c_str());
}

/*
SciTE received a macro command from director : execute it.
If command needs answer (SCI_GETTEXTLENGTH ...) : give answer to director
*/

static uptr_t ReadNum(const WCHAR *&t) {
    const WCHAR *argend = wcschr(t, L';');	// find ';'
    uptr_t v = 0;
    if (*t)
        v = _wtoi(t);					// read value
    t = argend + 1;					// update pointer
    return v;						// return value
}

void SciTEBase::ExecuteMacroCommand(const WCHAR *command) {
    const WCHAR *nextarg = command;
    uptr_t wParam;
    sptr_t lParam = 0;
    int rep = 0;				//Scintilla's answer
    const WCHAR *answercmd;
    int l;
    WCHAR *string1 = NULL;
    WCHAR params[4];
    //params describe types of return values and of arguments
    //0 : void or no param
    //I : integer
    //S : string
    //R : return string (for lParam only)

    //extract message,wParam ,lParam

    uptr_t message = ReadNum(nextarg);
    wcsncpy(params, nextarg, 3);
    nextarg += 4;
    if (*(params + 1) == L'R') {
        // in one function wParam is a string  : void SetProperty(string key,string name)
        const WCHAR *s1 = nextarg;
        while (*nextarg != L';')
            nextarg++;
        int lstring1 = nextarg - s1;
        string1 = new WCHAR[lstring1 + 1];
        if (lstring1 > 0)
            wcsncpy(string1, s1, lstring1);
        *(string1 + lstring1) = L'\0';
        wParam = reinterpret_cast<uptr_t>(string1);
        nextarg++;
    } else {
        wParam = ReadNum(nextarg);
    }

    if (*(params + 2) == L'S')
        lParam = reinterpret_cast<sptr_t>(nextarg);
    else if (*(params + 2) == L'I')
        lParam = _wtoi(nextarg);

    if (*params == L'0') {
        // no answer ...
        SendEditor(message, wParam, lParam);
		delete []string1;
        return;
    }

    if (*params == L'S') {
        // string answer
        if (message == SCI_GETSELTEXT) {
            l = SendEditor(SCI_GETSELTEXT, 0, 0);
            wParam = 0;
        } else if (message == SCI_GETCURLINE) {
            int line = SendEditor(SCI_LINEFROMPOSITION, SendEditor(SCI_GETCURRENTPOS));
            l = SendEditor(SCI_LINELENGTH, line);
            wParam = l;
        } else if (message == SCI_GETTEXT) {
            l = SendEditor(SCI_GETLENGTH);
            wParam = l;
        } else if (message == SCI_GETLINE) {
            l = SendEditor(SCI_LINELENGTH, wParam);
        } else {
            l = 0; //unsupported calls EM
        }
        answercmd = L"stringinfo:";

    } else {
        //int answer
        answercmd = L"intinfo:";
        l = 30;
    }

    size_t alen = wcslen(answercmd);
    WCHAR *tbuff = new WCHAR[l + alen + 1];
    wcscpy(tbuff, answercmd);
    if (*params == L'S')
        lParam = reinterpret_cast<sptr_t>(tbuff + alen);

    if (l > 0)
        rep = SendEditor(message, wParam, lParam);
    if (*params == L'I')
        wsprintf(tbuff + alen, L"%0d", rep);
    extender->OnMacro(L"macro", tbuff);
    delete []tbuff;
}

/**
* Process all the command line arguments.
* Arguments that start with '-' (also '/' on Windows) are switches or commands with
* other arguments being file names which are opened. Commands are distinguished
* from switches by containing a ':' after the command name.
* The print switch /p is special cased.
* Processing occurs in two phases to allow switches that occur before any file opens
* to be evaluated before creating the UI.
* Call twice, first with phase=0, then with phase=1 after creating UI.
*/
bool SciTEBase::ProcessCommandLine(SString &args, int phase) {
    bool performPrint = false;
    bool evaluate = phase == 0;
    StringList wlArgs(true);
    wlArgs.Set(args.c_str());
    for (int i = 0; i < wlArgs.len; i++) {
        WCHAR *arg = wlArgs[i];
        if (IsSwitchCharacter(arg[0])) {
            arg++;
            if (arg[0] == L'\0' || (arg[0] == L'-' && arg[1] == L'\0')) {
                if (phase == 1) {
                    OpenFromStdin(arg[0] == L'-');
                }
            } else if (arg[0] == L'@') {
                if (phase == 1) {
                    OpenFilesFromStdin();
                }
            } else if ((tolower(arg[0]) == L'p') && (wcslen(arg) == 1)) {
                performPrint = true;
            } else if (wcscmp(arg, L"grep") == 0) {
                // wlArgs[i+1] will be options in future
                GrepFlags gf = grepStdOut;
                if (wlArgs[i+1][0] == L'w')
                    gf = static_cast<GrepFlags>(gf | grepWholeWord);
                if (wlArgs[i+1][1] == L'c')
                    gf = static_cast<GrepFlags>(gf | grepMatchCase);
                if (wlArgs[i+1][2] == L'd')
                    gf = static_cast<GrepFlags>(gf | grepDot);
                if (wlArgs[i+1][3] == L'b')
                    gf = static_cast<GrepFlags>(gf | grepBinary);
                WCHAR unquoted[1000];
                wcscpy(unquoted, wlArgs[i+3]);
                UnSlash(unquoted);
                InternalGrep(gf, FilePath::GetWorkingDirectory().AsInternal(), wlArgs[i+2], unquoted);
                exit(0);
            } else {
                if (AfterName(arg) == L':') {
                    if (isprefix(arg, L"open:") || isprefix(arg, L"loadsession:")) {
                        if (phase == 0)
                            return performPrint;
                        else
                            evaluate = true;
                    }
                    if (evaluate)
                        PerformOne(arg);
                } else {
                    if (evaluate) {
                        props.ReadLine(arg, true, FilePath::GetWorkingDirectory());
                    }
                }
            }
        } else {	// Not a switch: it is a file name
            if (phase == 0)
                return performPrint;
            else
                evaluate = true;

            InitialiseBuffers();
            if (props.GetInt(L"save.recent"))
                RestoreRecentMenu();

            if (!PreOpenCheck(arg))
                Open(arg, ofQuiet);
        }
    }
    if (phase == 1) {
        // If we have finished with all args and no buffer is open
        // try to load session.
        if (!buffers.initialised) {
            InitialiseBuffers();
            if (props.GetInt(L"save.recent"))
                RestoreRecentMenu();
            if (props.GetInt(L"buffers") && props.GetInt(L"save.session"))
                RestoreSession();
        }
        // No open file after session load so create empty document.
        if (filePath.IsUntitled() && buffers.length == 1 && !buffers.buffers[0].isDirty) {
            Open(L"");
        }
    }
    return performPrint;
}

// Implement ExtensionAPI methods
sptr_t SciTEBase::Send(Pane p, unsigned int msg, uptr_t wParam, sptr_t lParam) {
    if (p == paneEditor)
        return SendEditor(msg, wParam, lParam);
    else
        return SendOutput(msg, wParam, lParam);
}

WCHAR *SciTEBase::Range(Pane p, int start, int end) {
    int len = end - start;
    WCHAR *s = new WCHAR[len + 1];
    if (p == paneEditor)
        GetRange(wEditor, start, end, s);
    else
        GetRange(wOutput, start, end, s);
    return s;
}

void SciTEBase::Remove(Pane p, int start, int end) {
    // Should have a scintilla call for this
    if (p == paneEditor) {
        SendEditor(SCI_SETSEL, start, end);
        SendEditor(SCI_CLEAR);
    } else {
        SendOutput(SCI_SETSEL, start, end);
        SendOutput(SCI_CLEAR);
    }
}

void SciTEBase::Insert(Pane p, int pos, const WCHAR *s) {
    if (p == paneEditor)
        SendEditorString(SCI_INSERTTEXT, pos, s);
    else
        SendOutputString(SCI_INSERTTEXT, pos, s);
}

void SciTEBase::Trace(const WCHAR *s) {
    MakeOutputVisible();
    OutputAppendStringSynchronised(s);
}

WCHAR *SciTEBase::Property(const WCHAR *key) {
    SString value = props.GetExpanded(key);
    WCHAR *retval = new WCHAR[value.length() + 1];
    wcscpy(retval, value.c_str());
    return retval;
}

void SciTEBase::SetProperty(const WCHAR *key, const WCHAR *val) {
    SString value = props.GetExpanded(key);
    if (value != val) {
        props.Set(key, val);
        needReadProperties = true;
    }
}

void SciTEBase::UnsetProperty(const WCHAR *key) {
    props.Unset(key);
    needReadProperties = true;
}

uptr_t SciTEBase::GetInstance() {
    return 0;
}

void SciTEBase::ShutDown() {
    QuitProgram();
}

void SciTEBase::Perform(const WCHAR *actionList) {
    WCHAR *actionsDup = StringDup(actionList);
    WCHAR *actions = actionsDup;
    WCHAR *nextAct;
    while ((nextAct = wcschr(actions, L'\n')) != NULL) {
        *nextAct = L'\0';
        PerformOne(actions);
        actions = nextAct + 1;
    }
    PerformOne(actions);
    delete []actionsDup;
}

void SciTEBase::DoMenuCommand(int cmdID) {
    MenuCommand(cmdID, 0);
}

